如何在 zsh 中正确收集行数组

如何在 zsh 中正确收集行数组

我认为以下内容会将 的输出分组my_command为行数组:

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

因此,这$array_of_lines[1]将引用 输出中的第一行my_command$array_of_lines[2]第二行,依此类推。

然而,上面的命令似乎不太好用。正如我检查过的那样,它似乎还分割了my_command字符周围的输出,我相信它会逐行打印数组的元素。我还检查了这一点:nprint -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?为什么上面的方法不起作用?

答案1

TL、博士:

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其非默认值,因此它们可能会弄乱后续代码。要保留 local 设置IFS,请将整个内容放入声明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打印的命令(→ Q3)。$array_of_lines

答案2

有两个问题:首先,显然双引号也不能解释反斜杠转义(对此感到抱歉:)。使用$'...'引号。根据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

我无法回答你的问题3。我希望我永远不需要知道如此深奥的事情:)。

答案3

您还可以使用 tr 将换行符替换为空格:

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

相关内容