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


12
list=`ls -a R*`
echo $list

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

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


6
一般注意事项:请勿对ls执行此操作,否则它将在任何奇怪的文件名上中断。看这里
terdon

Answers:


12

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

echo "$list"

否则,外壳程序将在空白处(空格,换行符,制表符)扩展和分割变量内容,而这些内容将丢失。


15

与其不明智地将ls输出放入变量中然后对其进行echo处理(这会删除所有颜色),请使用

ls -a1

man ls

       -1     list one file per line.  Avoid '\n' with -q or -b

我不建议您使用的输出做任何事情ls,除非显示它:)

例如,使用Shell Glob和for循环来处理文件...

shopt -s dotglob                  # to include hidden files*

for i in R/*; do echo "$i"; done

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


1
我建议更换:echo "$i"printf "%s\n" "$i" (如果一个文件名以启动一个不会呛“ - ”)。实际上,大多数情况下echo something应替换为printf "%s\n" "something"
奥利维尔·杜拉克

2
@OlivierDulac他们都从R这里开始,但是总的来说是的。然而,即使是脚本编写,总是尝试容纳-路径的开头也会带来麻烦:人们认为--,只有某些命令支持,这表示选项结束。我看过find . [tests] -exec [cmd] -- {} \;哪里[cmd]不支持--.无论如何,那里的每条路都开始!但是,这对替换没有参数echoprintf '%s\n'在这里,可以用替换整个循环 printf '%s\n' R/*。Bash具有printf内置功能,因此实际上对参数的数量/长度没有限制。
伊莱亚·卡根

12

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

IFS=$'\n' dirs=( $(find . -type d) )

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

现在,由于某些bash异常(请参阅此处),IFS执行此操作后需要将其重置为原始值。因此,请保存并重新分配:

oldIFS="$IFS"
IFS=$'\n' dirs=( $(find . -type d) ) 
IFS="$oldIFS"

或手动执行:

IFS=" "$'\t\n '

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


我可能已经提出过类似的建议,但是关于duOP 的部分对保留输出格式更感兴趣。
muru

如果目录名称包含空格,该如何工作?
甜点

@甜点哦!现在应该可以使用空格(但不能使用换行符)。感谢您指出。
terdon

1
请注意,在分配该阵列后,应重置或取消设置IFS。unix.stackexchange.com/q/264635/70524
muru

@muru哦,哇,谢谢,我忘记了那种怪异。
terdon

2

如果必须存储输出并想要一个数组,则mapfile变得很容易。

首先,考虑是否需要存储命令的输出。如果不这样做,只需运行命令。

如果您决定要将命令的输出读取为行的数组,那么的确可以做到这一点的一种方法是禁用globbing,设置IFS为按行拆分,在( )数组创建语法中使用命令替换,然后进行重置IFS,这比看起来要复杂Terdon的答案涵盖了某些方法。但我建议您使用mapfileBash shell的内置命令来代替以行数组形式读取文本。这是一个从文件读取的简单情况:

mapfile < filename

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

mapfile lines < filename

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

mapfile -t records < filename

您也可以使用-t而不传递数组名称;也就是说,它也可以使用隐式数组名MAPFILE

所述mapfile内置支撑件壳的其他选项,并且它可以替代地被援引为readarray。运行help mapfile(和help readarray)以获取详细信息。

但是您不想从文件中读取,而是想从命令的输出中读取。为此,请使用流程替换。此命令读取从命令线some-commandarguments...作为其命令行参数并在它们的地方mapfile的默认阵列MAPFILE,与其端接换行符移除:

mapfile -t < <(some-command arguments...)

进程替换用实际的文件名代替,可以从中读取运行的输出。该文件是命名管道,而不是常规文件,在Ubuntu上,它的命名方式为,(有时用以外的其他编号表示),但是您无需担心细节,因为shell会处理它所有的幕后。<(some-command arguments...)some-command arguments.../dev/fd/6363

您可能会认为可以通过改用来代替进程替换,但是那样行不通,因为当您有多个由分隔的命令组成的管道时,Bash运行会在subshel​​l中运行所有命令。这样既和自己的运行环境,从初始化,但是从在其中运行的管道外壳的环境中分离出来。在运行的子外壳中,确实填充了该阵列,但是当命令结束时,该阵列将被丢弃。永远不会为调用者创建或修改。some-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE

以下是该示例terdon的回答的样子,在整体,如果你使用mapfile

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

而已。您无需检查是否IFS已设置,请跟踪是否已设置以及设置了什么值,将其设置为换行符,然后稍后进行重置或重新设置。您不必禁用通配符(例如,使用set -f),如果您要认真使用该方法,则确实需要使用通配符,因为文件名可能包含*?,然后[再重新启用(set +f)。

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

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

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

真的没有一种万能的解决方案。

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

只需使用一个find命令即可更好地实现上面显示的任务:

find . -type d -printf 'DIR: %p\n'

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

find . -type d | sed 's/^/DIR: /'

如果您需要列出并还对找到的每个目录执行操作,例如运行some-command和传递目录路径作为参数,则也find可以执行以下操作:

find . -type d -print -exec some-command {} \;

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

help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.