Вопрос или проблема
Существует ли портативный способ объединения нескольких строк с использованием заданного разделителя в unix shell scripting, как показано ниже:
$ strjoin --- foo bar baz quux
foo---bar---baz---quux
Конечно, я мог бы использовать однострочный код на $языке_скриптов или некрасивый явный цикл в функции shell-скрипта, но старые unix-хакеры, вероятно, тоже имели в этом нужду, так что кто-то сделал стандартную команду вроде этой, о которой я не знаю, верно?
правка
Метод sed
определенно является самым простым в многих ситуациях, но он не работает, если строки могут содержать пробелы. И многие другие ответы также не справляются с этой задачей. Есть ли решения, помимо трюка с $IFS
, которые обрабатывают пробелы (и все возможные символы в целом) и не требуют написания полного цикла?
Для разделителей длиной более одного символа можно использовать:
-
sed
(как уже указал @Mark)$ echo foo bar baz quux | sed "s/ /---/g"
-
ex
$ echo foo bar baz quux | ex +"s/ /---/gp" -cq! /dev/stdin $ ex +"s/ /---/gp" -scq! <(echo foo bar baz quux)
-
printf
(но он покажет лишний завершающий разделитель)$ printf "%s---" foo bar baz quux
Для разделителей длиной в один символ можно:
-
использовать следующую функцию shell (в соответствии с этим постом SO):
join_by { local IFS="$1"; shift; echo "$*"; }
Использование:
$ join_by '-' foo bar baz quux
-
использовать
tr
echo foo bar baz quux | tr ' ' '-'
Лучший метод, который я нашел, это некрасивый явный цикл, о котором вы упомянули.
join(){
# Если нет аргументов, ничего не делать.
# Это избегает путаницы с ошибками в некоторых оболочках.
if [ $# -eq 0 ]; then
return
fi
local joiner="$1"
shift
while [ $# -gt 1 ]; do
printf "%s%s" "$1" "$joiner"
shift
done
printf '%s\n' "$1"
}
Использование:
$ join --- foo bar baz quux
foo---bar---baz---quux
Проверено с Bash, Dash и Zsh на Ubuntu, и должно работать в других оболочках, основанных на Bourne.
Perl не такой сложный для простых операций:
$ perl -e 's/ /---/g'
lam
Вот пример, использующий команду lam
:
$ SEP="---"; lam <(echo foo) -s$SEP <(echo bar) -s$SEP <(echo baz) -s$SEP <(echo quux)
foo---bar---baz---quux
paste
Если разделитель состоит из одного символа, то можно использовать команду paste
:
$ printf "%s\n" foo bar baz quux | paste -sd-
foo-bar-baz-quux
В дополнение к комментарию @embobo (который, надеюсь, скоро станет ответом), perl
можно использовать для разделения и объединения произвольных строк. Это сложнее, чем использование sed
, и, исходя из приведенного выше примера, будет чрезмерным решением.
awk
версия:
function join(a, start, end, sep, result, i) {
sep = sep ? sep : " "
start = start ? start : 1
end = end ? end : sizeof(a)
if (sep == SUBSEP) # волшебное значение
sep = ""
result = a[start]
for (i = start + 1; i <= end; i++)
result = result sep a[i]
return result
}
Вызовите его с gawk
с --source
вашими строками:
$ gawk -f join.awk --source 'BEGIN { split("foo bar quux",a); print join(a,1,3,"---") }'
foo---bar---quux
Версия для shell-скрипта:
function join() {
for i in "$@"; do
echo -n "$i""---"
done
echo
}
join foo bar baz quux
Вызовите его и обрежьте последний разделитель:
$ ./join.sh | sed 's/\-\-\-$//'
foo---bar---baz---quux
python -c 'import sys; print "__".join(sys.argv[1:])' a b c
function join_by() {
local L_IFS=$1
shift
python -c "import sys; print(\"$L_IFS\".join(sys.argv[1:]))" "$@"
}
Не уверен, насколько это портативно, но если:
(1) строки находятся в массиве, и
(2) массив содержит как минимум два элемента,
то я выведу первую строку и объединю ее с последовательностью оставшихся строк, предваренных разделителем; последний можно получить с помощью команды ‘printf’.
Вот что я придумал:
SEP='---'
STRINGS=( 'foo' 'bar' 'baz' 'quux' )
echo "${STRINGS[0]}$(printf -- "${SEP//%/%%}"'%s' "${STRINGS[@]:1}")"
Это работает, по крайней мере, в bash, и, я думаю, охватывает все случаи (при условии, что массив ‘STRINGS’ содержит как минимум два элемента), включая строку-разделитель, которая начинается с дефиса, или строку-разделитель, содержащую один или несколько символов процента.
Ответ или решение
Вопрос о том, как объединить строки с заданным разделителем в Unix, затрагивает несколько аспектов, включая переносимость, обработку пробелов и возможности стандартных команд. Ниже приведено несколько методов, которые могут помочь в решении данной задачи.
1. Использование printf
Один из простых способов объединить строки с разделителем — это использовать команду printf
. Однако этот метод добавляет лишний разделитель в конце:
$ printf "%s---" foo bar baz quux
foo---bar---baz---quux---
Для устранения лишнего разделителя можно использовать sed
:
$ printf "%s---" foo bar baz quux | sed 's/---$//'
foo---bar---baz---quux
2. Функция на Bash
Создание функции на Bash может быть универсальным и простым решением. Вот пример функции, которая точно объединяет строки:
join() {
if [ $# -eq 0 ]; then
return
fi
local joiner="$1"
shift
while [ $# -gt 1 ]; do
printf "%s%s" "$1" "$joiner"
shift
done
printf '%s\n' "$1"
}
# Использование
$ join --- foo bar baz quux
foo---bar---baz---quux
Эта функция была протестирована на Bash, Dash и Zsh и должна работать в других оболочках, основанных на Bourne.
3. Использование paste
Если разделитель состоит из одного символа, можно воспользоваться командой paste
. Этот метод очень удобен:
$ printf "%s\n" foo bar baz quux | paste -sd-
foo-bar-baz-quux
Однако этот метод не подойдет, если разделитель многосимвольный.
4. Использование awk
Еще одним подходом является использование awk
, который включает в себя функции для соединения строк:
function join(a, start, end, sep, result, i) {
sep = sep ? sep : " "
start = start ? start : 1
end = end ? end : sizeof(a)
result = a[start]
for (i = start + 1; i <= end; i++)
result = result sep a[i]
return result
}
# Пример вызова
$ gawk -f join.awk --source 'BEGIN { split("foo bar quux",a); print join(a,1,3,"---") }'
foo---bar---quux
5. Использование Perl
Perl также может быть использован для объединения строк:
$ perl -e 'print join("---", @ARGV)' foo bar baz quux
foo---bar---baz---quux
Это универсальное решение, но возможно, не самое простой для выполнения.
6. Использование tr
Для простых случаев можно использовать tr
, который заменяет знаки пробелов на заданный разделитель:
echo foo bar baz quux | tr ' ' '-'
foo-bar-baz-quux
Этот метод также имеет ограничения, так как не подходит для многосимвольных разделителей.
Заключение
В зависимости от ваших требований и контекста, вы можете выбрать одно из вышеперечисленных решений. Использование функций Bash является наиболее гибким способом, который позволяет избежать проблем с лишними разделителями и обработкой пробелов. Надеюсь, что эти методы помогут вам в вашей задаче объединения строк в Unix-подобных системах.