Как правильно собрать массив строк в 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);

но я получил тот же результат, что и без него.

Наконец, следуя ответу об элементах List с пробелами в 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со значением, отличным от значения по умолчанию, поэтому они могут испортить последующий код. Чтобы сохранить настройку IFSlocal, поместите все это в функцию, в которой вы объявляете IFSlocal (здесь также заботится о сохранении статуса выхода команды):

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[@]". Эта "$@"вещь для сохранения всех элементов массива происходит из оболочки Bourne в конце 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
Jimchao
источник
3
что если строки содержат пробелы?
don_crissti
2
Это бессмысленно. И SPC, и NL имеют значение по умолчанию $IFS. Перевод одного на другой не имеет значения.
Стефан Шазелас
Были ли мои правки разумными? Я не мог заставить его работать, как это было,
Джон П
(тайм-аут) Я признаю, что отредактировал его, не понимая, каково было намерение, но я думаю, что перевод - хорошее начало для разделения, основанного на манипуляции со строками. Я не стал бы переводить на пробелы и разделять их, если бы ожидаемое поведение не было больше похоже на то echo, где входные данные представляют собой более или менее кучу слов, разделенных тем, кого это волнует.
Джон П