如何将命令输出分隔为单独的行

Ano*_*ony 12 bash scripts

list=`ls -a R*`
echo $list
Run Code Online (Sandbox Code Playgroud)

在 shell 脚本中,这个 echo 命令将列出当前目录中以 R 开头的所有文件,但在一行中。如何将每个项目打印在一行上?

对于, , 等发生的所有场景ls,我需要一个通用命令。dufind -type -d

Zan*_*nna 16

与其不明智地将ls输出放入变量中,然后echo删除所有颜色,不如使用

ls -a1
Run Code Online (Sandbox Code Playgroud)

man ls

       -1     list one file per line.  Avoid '\n' with -q or -b
Run Code Online (Sandbox Code Playgroud)

我不建议你对 的输出做任何事情ls,除了显示它:)

例如,使用 shell globs 和for循环来处理文件...

shopt -s dotglob                  # to include hidden files*

for i in R/*; do echo "$i"; done
Run Code Online (Sandbox Code Playgroud)

*这将不包括当前目录.或其父..

  • @OlivierDulac 他们在这里都以“R”开头,但总的来说,是的。然而,即使对于脚本,尝试*总是*在路径中容纳前导 `-` 会导致麻烦:人们认为 `--`,只有某些命令支持,表示选项结束。我见过`找到。[tests] -exec [cmd] -- {} \;` 其中 `[cmd]` 不支持 `--`。无论如何,那里的每条路径都以`.`开头!*但这并不是反对用 `printf '%s\n'` 替换 `echo` 的论据。* 在这里,可以用 `printf '%s\n' R/*` 替换整个循环。Bash 内置了 `printf`,因此实际上对参数的数量/长度没有限制。 (2认同)

mur*_*uru 13

如果命令的输出包含多行,则在回显时引用您的变量以保留这些换行符:

echo "$list"
Run Code Online (Sandbox Code Playgroud)

否则,shell 将在空格(空格、换行符、制表符)上扩展和拆分变量内容,而这些内容将丢失。


ter*_*don 12

虽然像@muru建议的那样将它放在引号中确实可以满足您的要求,但您可能还需要考虑为此使用数组。例如:

IFS=$'\n' dirs=( $(find . -type d) )
Run Code Online (Sandbox Code Playgroud)

IFS=$'\n'是告诉bash只分裂上换行符characctersø输出得到的数组的每个元素。没有它,它将在空间上分裂,因此a file name with spaces.txt将是 5 个单独的元素而不是一个。但是,如果您的文件/目录名称可以包含换行符 ( \n),则此方法将中断。它将命令输出的每一保存为数组的一个元素。

请注意,我也改变了旧式`command`$(command)这是首选的语法。

您现在有一个名为 的数组$dirs,每个 bof 的元素都是前一个命令输出的一行。例如:

$ find . -type d
.
./olad
./ho
./ha
./ads
./bar
./ga
./da
$ IFS=$'\n' dirs=( $(find . -type d) )
$ for d in "${dirs[@]}"; do
    echo "DIR: $d"
  done
DIR: .
DIR: ./olad
DIR: ./ho
DIR: ./ha
DIR: ./ads
DIR: ./bar
DIR: ./ga
DIR: ./da
Run Code Online (Sandbox Code Playgroud)

现在,由于一些 bash 的奇怪之处(请参阅此处),您需要IFS在执行此操作后将其重置回原始值。所以,要么保存它并重新签名:

oldIFS="$IFS"
IFS=$'\n' dirs=( $(find . -type d) ) 
IFS="$oldIFS"
Run Code Online (Sandbox Code Playgroud)

或者手动执行:

IFS=" "$'\t\n '
Run Code Online (Sandbox Code Playgroud)

或者,只需关闭当前终端。您的新 IFS 将再次设置原始 IFS。


Eli*_*gan 5

如果您必须存储输出并且需要一个数组,mapfile那么这很容易。

首先,考虑是否需要存储命令的输出。如果没有,只需运行命令即可。

如果您决定将命令的输出作为行数组读取,确实,一种方法是禁用通配符,设置为按行IFS拆分,在( )数组创建语法中使用命令替换,然后重置IFS,这比看起来更复杂。terdon 的回答涵盖了其中的一些方法。但我建议您改用mapfileBash shell 的内置命令 来将文本读取为行数组。这是一个从文件读取的简单情况:

mapfile < filename
Run Code Online (Sandbox Code Playgroud)

这会将行读入名为 的数组中MAPFILE。如果没有输入重定向,您将从 shell 的 标准输入(通常是您的终端)读取内容,而不是从. 要读入默认数组以外的数组,请传递其名称。例如,读入数组:< filenamefilenameMAPFILElines

mapfile lines < filename
Run Code Online (Sandbox Code Playgroud)

您可能选择更改的另一个默认行为是行尾的换行符保留在原处;它们显示为每个数组元素中的最后一个字符(除非输入结束时没有换行符,在这种情况下最后一个元素没有换行符)。要删除这些换行符以使它们不会出现在数组中,请将选项传递-tmapfile. 例如,这会读取数组records,但不会将尾随换行符写入其数组元素:

mapfile -t records < filename
Run Code Online (Sandbox Code Playgroud)

您也可以-t在不传递数组名称的情况下使用;也就是说,它MAPFILE也适用于隐式数组名称 。

shellmapfile内置支持其他选项,并且也可以作为readarray. 运行help mapfile(和help readarray) 了解详细信息。

但是您不想从文件中读取,而是想从命令的输出中读取。为此,请使用进程替换。此命令从命令中读取行some-command作为arguments...其命令行参数,并将其mapfile放入 的默认数组中MAPFILE,并删除终止换行符:

mapfile -t < <(some-command arguments...)
Run Code Online (Sandbox Code Playgroud)

进程替换替换为实际的文件名,可以从中读取运行的输出。该文件是一个命名管道而不是常规文件,在 Ubuntu 上,它将被命名为(有时使用除 之外的其他数字),但您不需要关心细节,因为 shell 会处理它一切都在幕后。<(some-command arguments...)some-command arguments.../dev/fd/6363

您可能认为可以通过使用来放弃进程替换,但这行不通,因为当您拥有由 分隔的多个命令的管道时,Bash 运行会在子 shell中运行所有命令。因此, 和都在自己的环境中运行,这些环境是从运行管道的 shell 环境中初始化但又与之分离的。在运行的子 shell 中,数组确实会被填充,但当命令结束时该数组将被丢弃。永远不会为调用者创建或修改。some-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE

如果您使用以下命令,那么terdon 的答案中的示例完整如下mapfile

mapfile -t dirs < <(find . -type d)
Run Code Online (Sandbox Code Playgroud)
for d in "${dirs[@]}"; do
    echo "DIR: $d"
done
Run Code Online (Sandbox Code Playgroud)

就是这样。您不需要检查是否IFS已设置,跟踪是否已设置以及设置的值,将其设置为换行符,然后重置或重新取消设置。您不必禁用通配符(例如,使用set -f)——如果您要认真使用该方法,则确实需要这样做,因为文件名可能包含*?[--然后重新启用它 ( set +f)。

您还可以用单个printf命令完全替换该特定循环 - 尽管这实际上并不是 的好处,因为无论您使用或其他方法来填充数组,mapfile您都可以这样做。mapfile这是一个较短的版本:

mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Run Code Online (Sandbox Code Playgroud)

重要的是要记住,正如terdon 提到的那样,逐行操作并不总是合适的,并且不能正确处理包含换行符的文件名。我建议不要以这种方式命名文件,但这可能会发生,包括偶然发生。

确实没有一种放之四海而皆准的解决方案。

您要求“适用于所有场景的通用命令”,以及使用mapfile某种方法接近该目标的方法,但我敦促您重新考虑您的要求。

只需一个find命令就可以更好地完成上面显示的任务:

find . -type d -printf 'DIR: %p\n'
Run Code Online (Sandbox Code Playgroud)

您还可以使用外部命令,例如sed添加DIR: 到每行的开头。这可以说有点丑陋,与 find 命令不同,它会在包含换行符的文件名中添加额外的“前缀”,但无论其输入如何,它都可以工作,因此它可以满足您的要求,并且仍然比将输出读入变量或数组

find . -type d | sed 's/^/DIR: /'
Run Code Online (Sandbox Code Playgroud)

如果您需要列出并在找到的每个目录上执行操作,例如运行some-command目录的路径并将其作为参数传递,find您也可以这样做:

find . -type d -print -exec some-command {} \;
Run Code Online (Sandbox Code Playgroud)

作为另一个例子,让我们回到向每行添加前缀的一般任务。假设我想查看输出help mapfile但对行进行编号。我实际上不会使用mapfile它,也不会使用任何其他将其读入 shell 变量或 shell 数组的方法。假设help mapfile | cat -n没有给出我想要的格式,我可以使用awk

find . -type d -print -exec some-command {} \;
Run Code Online (Sandbox Code Playgroud)

将命令的所有输出读入单个变量或数组有时是有用且合适的,但它有很大的缺点。当现有命令或现有命令的组合可能已经满足或更好地满足您的需要时,您不仅需要处理使用 shell 的额外复杂性,而且命令的整个输出都必须存储在内存中。有时您可能知道这不是问题,但有时您可能正在处理一个大文件。

read -r通常尝试的另一种方法(有时做得正确)是在循环中逐行读取输入。如果您在操作后面的行时不需要存储前面的行,并且必须使用长输入,那么它可能比mapfile. 但在大多数情况下,当您可以将其传递给可以完成工作的命令时,也应该避免这种情况