为什么 echo "$out" 将输出分成多行,引号抑制分词?
Why does echo "$out" split output onto multiple lines, if quotes suppress word-splitting?
我有一个非常简单的目录,里面有 "directory1" 和 "file2"。
之后
out=`ls`
我想打印我的变量:echo $out
给出:
directory1 file2
但 echo "$out"
给出:
directory1
file2
所以使用引号让我在单独的行上输出每条记录。正如我们所知,ls
命令对所有 files/dirs 使用单行打印输出(如果行足够大以包含输出)所以我预计使用双引号会阻止我的 shell 将单词拆分为单独的省略引号的行会将它们分开。
请告诉我:为什么使用引号(用于防止分词)突然拆分输出?
关于 ls
的行为
ls
默认情况下仅在单行上打印多个文件名 当输出到 TTY 时。当输出到管道、文件或类似文件时,默认是将一行打印到文件。
引自 the POSIX standard for ls
,并强调:
The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.
文字问题(回复:引用)
正是将您的命令拆分为单独参数的行为才导致它被放在一行中! 本地,你的值跨越多行,所以回显它未修改(没有任何分割)精确地打印它。
您的命令的结果类似于:
out='directory1
file2'
当您 运行 echo "$out"
时,将打印 确切的 内容。相比之下,当您 运行 echo $out
时,行为类似于:
echo "directory1" "file2"
...因为字符串被分成两个元素,每个元素都作为完全不同的参数传递给 echo
,以便 echo
按照它认为合适的方式进行处理——在这种情况下,在同一行打印这两个参数。
分词的副作用
分词可能看起来 就像您在这里想要的那样,但通常情况并非如此!考虑一些特殊问题:
- 分词扩展 glob 表达式:如果文件名包含一个被空格包围的
*
,则 *
将替换为当前目录中的文件列表,从而导致重复结果。
- 分词不支持引号或转义:如果文件名包含空格,则无法将该内部空格与分隔多个名称的空格区分开来。这与BashFAQ #50.
中描述的问题密切相关
阅读目录
参见 Why you shouldn't parse the output of ls
。简而言之——在您的 out=`ls`
示例中,out
变量(是一个字符串)无法以有用的、可解析的方式存储所有可能的文件名。
例如,考虑这样创建的文件:
touch $'hello\nworld"three words here"'
...该文件名包含空格和换行符,并且分词不会在 ls
的输出中将其正确检测为单个名称。但是,您可以在数组中存储和处理它:
# create an array of filenames
names=( * )
if ! [[ -e $names || -L $names ]]; then # this tests only the FIRST name
echo "No names matched" >&2 # ...but that's good enough.
else
echo "Found ${#files[@]} files" # print number of filenames
printf '- %q\n' "${names[@]}"
fi
我有一个非常简单的目录,里面有 "directory1" 和 "file2"。
之后out=`ls`
我想打印我的变量:echo $out
给出:
directory1 file2
但 echo "$out"
给出:
directory1
file2
所以使用引号让我在单独的行上输出每条记录。正如我们所知,ls
命令对所有 files/dirs 使用单行打印输出(如果行足够大以包含输出)所以我预计使用双引号会阻止我的 shell 将单词拆分为单独的省略引号的行会将它们分开。
请告诉我:为什么使用引号(用于防止分词)突然拆分输出?
关于 ls
的行为
ls
默认情况下仅在单行上打印多个文件名 当输出到 TTY 时。当输出到管道、文件或类似文件时,默认是将一行打印到文件。
引自 the POSIX standard for ls
,并强调:
The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.
文字问题(回复:引用)
正是将您的命令拆分为单独参数的行为才导致它被放在一行中! 本地,你的值跨越多行,所以回显它未修改(没有任何分割)精确地打印它。
您的命令的结果类似于:
out='directory1
file2'
当您 运行 echo "$out"
时,将打印 确切的 内容。相比之下,当您 运行 echo $out
时,行为类似于:
echo "directory1" "file2"
...因为字符串被分成两个元素,每个元素都作为完全不同的参数传递给 echo
,以便 echo
按照它认为合适的方式进行处理——在这种情况下,在同一行打印这两个参数。
分词的副作用
分词可能看起来 就像您在这里想要的那样,但通常情况并非如此!考虑一些特殊问题:
- 分词扩展 glob 表达式:如果文件名包含一个被空格包围的
*
,则*
将替换为当前目录中的文件列表,从而导致重复结果。 - 分词不支持引号或转义:如果文件名包含空格,则无法将该内部空格与分隔多个名称的空格区分开来。这与BashFAQ #50. 中描述的问题密切相关
阅读目录
参见 Why you shouldn't parse the output of ls
。简而言之——在您的 out=`ls`
示例中,out
变量(是一个字符串)无法以有用的、可解析的方式存储所有可能的文件名。
例如,考虑这样创建的文件:
touch $'hello\nworld"three words here"'
...该文件名包含空格和换行符,并且分词不会在 ls
的输出中将其正确检测为单个名称。但是,您可以在数组中存储和处理它:
# create an array of filenames
names=( * )
if ! [[ -e $names || -L $names ]]; then # this tests only the FIRST name
echo "No names matched" >&2 # ...but that's good enough.
else
echo "Found ${#files[@]} files" # print number of filenames
printf '- %q\n' "${names[@]}"
fi