Есть ли что-то вроде JavaScript «split ()» в оболочке?

18

Это очень легко использовать split()в JavaScript, чтобы разбить строку на массив.

А как насчет сценария оболочки?

Скажи, что я хочу сделать это:

$ script.sh var1_var2_var3

Когда пользователь передает такую ​​строку var1_var2_var3в script.sh, внутри скрипта он преобразует строку в массив, подобный

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done
AGamePlayer
источник
1
что shellвы используете, с чем bashвы можете сделатьIFS='_' read -a array <<< "${string}"
gwillie
perlможет сделать это тоже. Это не «чистая» оболочка, но она довольно распространенная.
Sobrique
@Sobrique Я также не знаю о техническом определении «чистой» оболочки, но есть node.js.
Эмори
Я склонен работать над тем, «установлен ли он по умолчанию на моем linux box», и не волнуюсь по мелочам :)
Sobrique

Ответы:

24

Оболочки типа Bourne / POSIX имеют оператор split + glob, и он вызывается каждый раз, когда вы оставляете в кавычках раскрытие параметров ( $var, $-...), команду substitution ( $(...)) или арифметическое расширение ( $((...))) без кавычек.

На самом деле, вы вызвали это по ошибке, когда вы сделали for name in ${array[@]}вместо for name in "${array[@]}". (На самом деле, вы должны помнить, что такой ошибочный вызов оператора является источником многих ошибок и уязвимостей в безопасности ).

Этот оператор настроен со $IFSспециальным параметром (чтобы указать, на какие символы разбивать (хотя имейте в виду, что пробел, табуляция и новая строка там получают специальную обработку)) и -fвозможностью отключить ( set -f) или включить ( set +f) globчасть.

Также обратите внимание, что в то время как Sin $IFSизначально был (в оболочке Bourne, откуда $IFSпроисходит) для Separator, в оболочках POSIX символы in $IFSлучше рассматривать как разделители или терминаторы (см. Пример ниже).

Итак, разделить на _:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

Чтобы увидеть различие между разделителем и разделителем , попробуйте:

string='var1_var2_'

Это разделит его на var1и var2только (без дополнительного пустого элемента).

Итак, чтобы сделать его похожим на JavaScript split(), вам понадобится дополнительный шаг:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(обратите внимание, что он разделит пустой элемент $stringна 1 (не 0 ), как в JavaScript split()).

Чтобы увидеть вкладку специальных процедур, пробел и перевод новой строки, сравните:

IFS=' '; string=' var1  var2  '

(где вы получаете var1и var2) с

IFS='_'; string='_var1__var2__'

где вы получите: '', var1, '', var2, ''.

Обратите внимание, что zshоболочка не вызывает этот оператор split + glob неявным образом, если только в shили kshэмуляция. Там вы должны вызывать это явно. $=stringдля части split, $~stringдля части glob ( $=~stringдля обеих), а также есть оператор split, где вы можете указать разделитель:

array=(${(s:_:)string})

или сохранить пустые элементы:

array=("${(@s:_:)string}")

Обратите внимание, что есть sдля разделения , а не разграничения (также с $IFSизвестным несоответствием POSIX zsh). Он отличается от JavaScript тем, split()что пустая строка разбита на 0 (не 1) элемент.

Заметная разница с $IFS-splitting заключается в том, что она ${(s:abc:)string}разделяется на abcстроку, тогда как с IFS=abc, которая разделяется на a, bили c.

С zshи ksh93, специальная обработка, которую получают пробел, табуляция или перевод строки, может быть удвоена $IFS.

Как историческое примечание, оболочка Bourne (предок или современные оболочки POSIX) всегда очищала пустые элементы. Также было несколько ошибок, связанных с разбиением и расширением $ @ со значениями не по умолчанию $IFS. Например IFS=_; set -f; set -- $@, не будет эквивалентно IFS=_; set -f; set -- $1 $2 $3....

Расщепление по регулярным выражениям

Теперь, для чего-то более похожего на JavaScript, split()который может разбиваться на регулярные выражения, вам нужно полагаться на внешние утилиты.

В сундуке с инструментами POSIX awkесть splitоператор, который может разбивать расширенные регулярные выражения (это более или менее подмножество Perl-подобных регулярных выражений, поддерживаемых JavaScript).

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

В zshоболочку встроена поддержка регулярных выражений, совместимых с Perl (в ее zsh/pcreмодуле), но использование ее для разделения строки, хотя и возможно, является относительно громоздким.

Стефан Шазелас
источник
Есть ли основания для специальных обработок с помощью табуляции, пробела и новой строки?
Cuonglm
1
@cuonglm, как правило, вы хотите разделить слова, когда разделители являются пробелами, в случае непустых разделителей (например, разделить $PATHна :), наоборот, вы обычно хотите сохранить пустые элементы. Обратите внимание, что в оболочке Bourne все персонажи получали специальную обработку, kshизменив ее так, чтобы обрабатывались только пустые (только пробел, табуляция и новая строка).
Стефан Шазелас
Что ж, недавняя добавленная заметка оболочки Bourne удивила меня. И для завершения, следует ли добавить примечание для zshобработки строкой содержит 2 или более символов в ${(s:string:)var}? Если добавлено, я могу удалить свой ответ :)
cuonglm
1
Что вы подразумеваете под «Также обратите внимание, что символ S в $ IFS предназначен для разделителя, а не разделителя».? Я понимаю механику и то, что она игнорирует конечные разделители, но Sобозначает разделитель , а не разделитель . По крайней мере, так написано в руководстве по моему bash.
Terdon
@terdon, $IFSпроисходит из оболочки Bourne, где он был разделителем , ksh изменил поведение без изменения имени. Я упомянул это, чтобы подчеркнуть, что split+glob(за исключением zsh или pdksh) просто больше не разделяется.
Стефан Шазелас
7

Да, используйте IFSи установите его _. Затем используйте read -aдля сохранения в массив ( -rотключает расширение с обратной косой чертой). Обратите внимание, что это относится к bash; ksh и zsh имеют сходные возможности с немного отличающимся синтаксисом, а обычный sh вообще не имеет переменных массива.

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

От man bash:

читать

-a aname

Слова присваиваются последовательным индексам переменной массива aname, начиная с 0. aname сбрасывается до назначения любых новых значений. Другие аргументы имени игнорируются.

IFS

Разделитель внутренних полей, который используется для разделения слов после раскрытия и разделения строк на слова с помощью встроенной команды read. Значением по умолчанию является `` ''.

Обратите внимание, что readостанавливается на первой новой строке. Перейдите -d ''к, readчтобы избежать этого, но в этом случае в конце будет добавлен новый перевод строки из-за <<<оператора. Вы можете удалить его вручную:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}
fedorqui
источник
Это предполагает, $rчто не содержит символов новой строки или обратной косой черты. Также обратите внимание, что он будет работать только в последних версиях bashоболочки.
Стефан Шазелас
@ StéphaneChazelas хорошая мысль. Да, это «основной» случай строки. В остальном каждый должен пойти на ваш исчерпывающий ответ. Что касается версий bash, read -aбыл введен в Bash 4, верно?
Федорки
1
извини, мой плохой, я думал, что <<<был добавлен только недавно, bashно кажется, что он был там с 2.05b (2002). read -aдаже старше этого. <<<приходит zshи поддерживается ksh93(и mksh и yash), но read -aзависит от bash (это -Aksh93, yash и zsh).
Стефан Шазелас
@ StéphaneChazelas есть ли какой-нибудь "простой" способ найти, когда произошли эти изменения? Я говорю «легко», чтобы не копаться в файлах релизов, возможно, на странице, показывающей их все.
Федорки
1
Я смотрю журналы изменений для этого. У zsh также есть репозиторий git с историей, начиная с 3.1.5, и его список рассылки также используется для отслеживания изменений.
Стефан Шазелас