Переносимый 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 содержит как минимум два элемента), включая строку-разделитель, начинающуюся с дефиса, или строку-разделитель, содержащую один или несколько знаков процента. .

Другие вопросы по тегам