Як правильно зібрати масив рядків у zsh


42

Я думав, що наступне буде групувати результат my_commandу масиві рядків:

IFS='\n' array_of_lines=$(my_command);

так, що $array_of_lines[1]буде посилатися на перший рядок у висновку my_command, $array_of_lines[2]на другий тощо.

Однак, здається, що команда вище не працює добре. Схоже, це також розділяє вихід my_commandнавколо персонажа n, як я це перевірив print -l $array_of_lines, який, на мій погляд, друкує елементи масиву рядок за рядком. Я також перевірив це за допомогою:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

Під час другої спроби я подумав, що додавання evalможе допомогти:

IFS='\n' array_of_lines=$(eval my_command);

але я отримав такий самий результат, як і без нього.

Нарешті, слідуючи відповіді на елементи списку з пробілами в zsh , я також спробував використовувати прапори розширення параметрів замість того, IFSщоб сказати zsh, як розділити вхід і зібрати елементи в масив, тобто:

array_of_lines=("${(@f)$(my_command)}");

Але я все одно отримав той самий результат (розкол відбувається на n)

З цим у мене є такі питання:

Q1. Які "правильні" способи збору результатів команди в масив рядків?

Q2. Як я можу вказати IFSрозділити лише на нові рядки?

Q3. Якщо я використовую прапори розширення параметрів, як у моїй третій спробі вище (тобто, використовуючи @f), щоб вказати поділ, чи zsh ігнорує значення IFS? Чому це не вийшло вище?

Відповіді:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

Перша помилка (→ Q2): IFS='\n'встановлює IFSдва символи \та n. Щоб встановити IFSновий рядок, використовуйте IFS=$'\n'.

Друга помилка: встановити змінну в значення масиву, вам потрібна дужка навколо елементів: array_of_lines=(foo bar).

Це спрацювало б, за винятком того, що воно знімає порожні рядки, оскільки послідовний пробіл вважається одним роздільником:

IFS=$'\n' array_of_lines=($(my_command))

Ви можете зберегти порожні рядки, крім самого кінця, подвоївши символ пробілу в IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Щоб зберегти також порожні рядки, вам доведеться додати щось до виводу команди, тому що це відбувається в самій підстановці команди, а не при її аналізі.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(якщо припустити, що результат my_commandне закінчується в неограниченому рядку; також зауважте, що ви втрачаєте статус виходу my_command)

Зауважте, що всі фрагменти, наведені вище, залишають IFSйого значення за замовчуванням, тому вони можуть зіпсувати наступний код. Щоб зберегти налаштування IFSлокального, введіть усе це у функцію, де ви оголосите IFSлокальну (тут також подбайте про збереження статусу виходу команди):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Але я рекомендую не возитися IFS; натомість використовуйте fпрапор розширення для розділення на нові рядки (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

Або для збереження порожніх рядків:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

Значення тут IFSне має значення. Я підозрюю, що ви використовували команду, яка розбивається на IFSдрук $array_of_linesу ваших тестах (→ Q3).


7
Це так складно! "${(@f)...}"те саме ${(f)"..."}, але по-іншому. (@)всередині подвійних лапок означає "вивести одне слово на елемент масиву" і (f)означає "розділити на масив за новим рядком". PS: Будь ласка, посилайтесь на документи
літаючі вівці

3
@flyingsheep, ніхто ${(f)"..."}не пропускатиме порожні рядки, "${(@f)...}"зберігає їх. Це те саме відмінність між $argvі "$argv[@]". Ця "$@"річ для збереження всіх елементів масиву походить із оболонки Борна наприкінці 70-х.
Стефан Шазелас

4

Два питання: по-перше, мабуть, подвійні лапки також не інтерпретують втечі зворотньої косої риски (вибачте про це :). Використовуйте $'...'цитати. І відповідно man zshparam, щоб зібрати слова в масив, потрібно укласти їх у дужки. Так це працює:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

Я не можу відповісти на ваш Q3. Сподіваюся, мені ніколи не доведеться знати таких езотеричних речей :).


-2

Tr також можна використовувати для заміни нової лінії на пробіл:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
що робити, якщо рядки містять пробіли?
don_crissti

2
В цьому немає сенсу. І SPC, і NL знаходяться за замовчуванням у $IFS. Переклад одного на інший не має значення.
Стефан Шазелас

Чи були мої правки обґрунтованими? Я не міг змусити його працювати так, як це було,
Джон П

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