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

如何将命令输出分成单独的行
list=`ls -a R*`
echo $list

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

我需要一个通用命令来应对所有与、、ls等相关的场景。dufind -type -d

答案1

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

echo "$list"

否则,shell 将在空白处(空格、换行符、制表符)扩展和分割变量内容,并且这些内容将会丢失。

答案2

不要不明智地将ls输出放在变量中,然后echo删除所有颜色,而是使用

ls -a1

man ls

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

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

例如,使用 shell globs 和for循环对文件执行某些操作...

shopt -s dotglob                  # to include hidden files*

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

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

答案3

将其放在引号中,如@muru建议确实会按照您的要求执行,您可能还想考虑使用数组来实现这一点。例如:

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

告诉IFS=$'\n'bash 只在换行符上拆分输出以获取数组的每个元素。如果没有它,它将在空格上拆分,因此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。

答案4

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

首先,考虑一下需要完全存储命令的输出。如果不存储,只需运行该命令即可。

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

mapfile < filename

这将读取行到名为 的数组中MAPFILE。没有输入重定向 < filename,你将从 shell 的标准输入(通常是你的终端)而不是由filename。要读入默认数组以外的数组MAPFILE,请传递其名称。例如,这将读入数组lines

mapfile lines < filename

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

mapfile -t records < filename

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

shellmapfile内置命令支持其他选项,也可以通过 调用readarray。运行help mapfile(和help readarray) 了解详情。

但是您不想从文件中读取,而是想从命令的输出中读取。为了实现这一点,使用流程替代.此命令从命令中读取行some-commandarguments...作为其命令行参数并将它们放置在mapfile的默认数组中MAPFILE,并删除其终止换行符:

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

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

你可能会认为你可以放弃进程替换,而是使用,但这是行不通的,因为当你有一个some-command arguments... | mapfile -t管道多个命令之间用 分隔|,Bash 运行所有命令子壳层。因此,和都以自己的方式运行some-command arguments...mapfile -t环境,由运行管道的 shell 环境初始化,但与该 shell 环境是分开的。在运行管道的子 shell 中mapfile -tMAPFILE数组得到填充,但是当命令结束时该数组会被丢弃。MAPFILE永远不会为调用者创建或修改。

以下是示例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。但是,它也是应该避免在可以将其通过管道传输到可以完成工作的命令的情况下,大多数情况下都是如此

相关内容