为什么 bash 忽略了 ls 输出中的引号?

  • 本文关键字:输出 ls bash bash
  • 更新时间 :
  • 英文 :


下面是一个脚本及其输出,描述了我今天发现的问题。即使ls输出被引用,bash仍然会在空格处中断。我改用for file in *.txt,只是想知道为什么bash这样做。

[chau@archlinux example]$ cat a.sh 
#!/bin/bash
FILES=$(ls --quote-name *.txt)
echo "Value of $FILES:"
echo $FILES
echo
echo "Loop output:"
for file in $FILES
do
echo $file
done
[chau@archlinux example]$ ./a.sh 
Value of $FILES:
"b.txt" "File with space in name.txt"
Loop output:
"b.txt"
"File
with
space
in
name.txt"

为什么 bash 忽略了 ls 输出中的引号?

因为单词拆分发生在变量扩展的结果上。

在计算语句时,shell 会经历不同的阶段,称为 shell 扩展。其中一个阶段是"分词"。从字面上看,单词拆分确实将您的变量拆分为单独的单词,引用了 bash 手册:

shell 扫描参数扩展、命令替换和算术扩展的结果,这些结果未在双引号内发生,以进行单词拆分。

shell 将$IFS的每个字符视为分隔符,并将其他扩展的结果拆分为使用这些字符作为字段终止符的单词。如果未设置 IFS,或者其值正好是<space><tab><newline>,则默认值,则忽略先前扩展结果开头和结尾的<space><tab><newline>序列,并且任何不在开头或结尾的 IFS 字符序列都用于分隔单词。...

当 shell 有一个不在双引号内的$FILES时,它首先进行"参数扩展"。它将$FILES扩展到字符串"b.txt" "File with space in name.txt"。然后发生分词。因此,使用默认IFS,生成的字符串在空格,制表符或换行符上拆分/分隔。

为了防止单词拆分,$FILES必须在双引号内,而不是$FILES的值。

好吧,你可以这样做(不安全(:

ls -1 --quote-name *.txt |
while IFS= read -r file; do
eval file="$file"
ls -l "$file"
done
  • 告诉ls输出换行符分隔列表-1
  • 逐行阅读列表
  • 重新评估变量以删除带有evil的引号。我的意思是eval
  • 我在循环中使用ls -l "$file"来检查"$file"是否是有效的文件名。

由于ls,这仍然不适用于所有文件名。带有不可读字符的文件名只是被我的ls忽略,就像touch "c.txt"$'x01'一样。带有嵌入式换行符的文件名会出现类似ls $'n'"c.txt".

这就是为什么建议忘记脚本中的ls-ls仅用于在终端中漂亮打印。在脚本中使用find.

如果您的文件名中没有嵌入换行符,您可以:

find . -mindepth 1 -maxdepth 1 -name '*.txt' |
while IFS= read -r file; do
ls -l "$file"
done

如果您的文件名只是任何内容,请使用以 null 结尾的流:

find . -mindepth 1 -maxdepth 1 -name '*.txt' -print0 |
while IFS= read -r -d'' file; do
ls -l "$file"
done

许多 unix 实用程序(grep -zxargs -0cut -zsort -z(都支持处理以零结尾的字符串/流,只是为了处理所有你可以拥有的奇怪文件名。

你可以试试下面的片段:

#!/bin/bash
while read -r file; do
echo "$file"
done < <(ls --quote-name *.txt)

最新更新