портативный unix способ соединения строк с разделителем

Вопрос или проблема

Существует ли портативный способ объединения нескольких строк с использованием заданного разделителя в 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-подобных системах.

Оцените материал
Добавить комментарий

Капча загружается...