Чи є щось подібне до "split ()" JavaScript в оболонці?


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

1
що shellти використовуєш, bashможеш зробитиIFS='_' read -a array <<< "${string}"
gwillie

perlможе це зробити також. Це не "чиста" оболонка, але вона досить поширена.
Sobrique

@Sobrique Я також не знаю про технічне визначення "чистої" оболонки, але є node.js.
emory

Я схильний працювати над "чи це, мабуть, встановлено на моєму linux box за замовчуванням", і не хвилюйтесь до дрібниць :)
Sobrique

Відповіді:


24

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

Насправді ви покликали це помилково, коли зробили for name in ${array[@]}замість цього for name in "${array[@]}". (Насправді, ви повинні бути обережними, що виклик такого оператора помилково є джерелом багатьох помилок та вразливості безпеки ).

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

Також зауважте, що в той час, як " Sin" $IFS(в оболонці Bourne, звідки $IFSпоходить) для Separator, в оболонках POSIX, символи в них $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оболонка не викликає оператора спліт-глобуля неявно подібного, якщо не є shабо kshемуляція. Там вам потрібно чітко посилатися на це. $=stringдля розділеної частини, $~stringдля глобальної частини ( $=~stringдля обох), а також він має роздільний оператор, де ви можете вказати роздільник:

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.

Як історична примітка, оболонка Борна (предка або сучасні оболонки 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на :), навпаки, ви взагалі хочете зберегти порожні елементи. Зауважте, що в оболонці Борна всі персонажі, які отримували спеціальну обробку, kshзмінили, щоб спеціально обробляли лише порожні (хоч лише пробіл, вкладку та новий рядок).
Стефан Шазелас

Що ж, нещодавно додана нота оболонки Борна мене здивувала. А для завершення, чи слід додати примітку до zshлікування рядком, що містить 2 або більше символів ${(s:string:)var}? Якщо додано, я можу видалити свою відповідь :)
cuonglm

1
Що ви маєте на увазі під «також зауважте, що S у $ IFS призначений для Delimiter, а не для роздільника.»? Я розумію механіку і що вона ігнорує кінцеві роздільники, але Sстоїть для роздільника , а не для роздільника . Принаймні, саме так говорить посібник з мого баша.
terdon

@terdon, $IFSпоходить від оболонки Борна, де був роздільником , 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:

читати

анам

Слова присвоюються послідовним індексам змінної анамеми масиву, починаючи з 0. анам не встановлюється перед призначенням нових значень. Інші аргументи назви ігноруються.

IFS

Внутрішній розділювач поля, який використовується для розбиття слів після розширення та для розділення рядків на слова за допомогою команди прочитаного вбудованого. Значення за замовчуванням - `` ''.

Зауважте, що readзупиняється на першому новому рядку. Перейдіть -d ''до цього, readщоб уникнути цього, але в такому випадку в кінці кінця з'явиться додаткова нова лінія завдяки <<<оператору. Ви можете видалити його вручну:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}

Це передбачає, $rщо не містить символів нового рядка чи зворотних косих рядків. Також зауважте, що він працюватиме лише в останніх версіях bashоболонки.
Стефан Шазелас

@ StéphaneChazelas хороший момент. Так, це "основний" випадок рядка. В іншому всі повинні звернутися за вашою вичерпною відповіддю. Щодо версій bash, read -aвведено в bash 4, правда?
fedorqui

1
Вибачте, що я поганий, я думав, що <<<його додали недавно, bashале, здається, він існував з 2.05b (2002). read -aнавіть старший за це. <<<походить від zshі підтримується також ksh93(і mksh і yash), але read -aє специфічним для bash (це -Aв ksh93, yash і zsh).
Stéphane Chazelas

@ StéphaneChazelas чи є якийсь "простий" спосіб знайти, коли ці зміни відбулися? Я кажу "легко" не копатись у файли випусків, можливо, на сторінці, де вони відображені всі.
fedorqui

1
Я переглядаю журнали змін для цього. zsh також має сховище git з історією, починаючи з 3.1.5, і його список розсилки використовується також для відстеження змін.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.