Переносимый Unix способ объединения строк с разделителем
Существует ли переносимый способ записи сценариев Unix для объединения нескольких строк вместе с заданным разделителем, например так:
$ strjoin --- foo bar baz quux
foo---bar---baz---quux
Конечно, я мог бы использовать $ scripting_language one liner или уродливый явный цикл в функции шеллскрипта, но старые хакеры unix, вероятно, тоже нуждались в этом, поэтому кто-то создал стандартную команду, подобную этой, о которой я не знаю где-то в прошлом, верно?
редактировать
sed
Метод, безусловно, самый простой во многих ситуациях, но он не работает, если строки могут содержать пробелы. И многие другие ответы также не справляются с этим. Есть ли какие-либо решения, кроме $IFS
Трюк, который обрабатывает пробелы (и все возможные символы в целом) и не требует написания полного цикла?
8 ответов
Для многосимвольного длинного разделителя вы можете использовать:
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
используя следующую функцию оболочки (согласно этому посту SO):
join_by { local IFS="$1"; shift; echo "$*"; }
Использование:
$ join_by '---' foo bar baz quux
Для односимвольных длинных разделителей вы можете использовать:
tr
echo foo bar baz quux | tr ' ' '-'
Лучший метод, который я нашел, - это уродливый явный цикл, который вы упомянули.
join(){
# If no arguments, do nothing.
# This avoids confusing errors in some shells.
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.
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
Perl не так сложен для простых операций:
$ perl -e 's/ /---/g'
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:]))" "$@"
}
awk
версия:
function join(a, start, end, sep, result, i) {
sep = sep ? sep : " "
start = start ? start : 1
end = end ? end : sizeof(a)
if (sep == SUBSEP) # magic value
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
Версия сценария оболочки:
function join() {
for i in "$@"; do
echo -n "$i""---"
done
echo
}
join foo bar baz quux
Назовите его и обрежьте последний разделитель:
$ ./join.sh | sed 's/\-\-\-$//'
foo---bar---baz---quux
В дополнение к комментарию @embobo (который, мы надеемся, скоро станет ответом), perl
может использоваться для разделения и объединения произвольных строк. Это сложнее, чем использование sed
и на основе приведенного выше примера будет серьезным излишним.
Не уверен, насколько это переносимо, но если:
(1) строки находятся в массиве и
(2) массив содержит как минимум два элемента,
то я бы вывел первую строку и объединил ее с последовательностью остальных строк. с префиксом разделителя; последний можно создать с помощью команды printf.
Вот что я придумал:
SEP='---'
STRINGS=( 'foo' 'bar' 'baz' 'quux' )
echo "${STRINGS[0]}$(printf -- "${SEP//%/%%}"'%s' "${STRINGS[@]:1}")"
Он работает, по крайней мере, в bash, и я думаю, что он охватывает все случаи (при условии, что массив STRINGS содержит как минимум два элемента), включая строку-разделитель, начинающуюся с дефиса, или строку-разделитель, содержащую один или несколько знаков процента. .