在跟踪 shellscript 中的错误时,我发现此代码片段中存在以下行为:
declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
sha256sum ${filelist[$file]} | head -c 64
done
当数组filelist
不包含在双引号中时,命令成功。我一直在使用 ShellCheck 来尝试改进我的编码,它建议-
双引号可防止通配符和分词。
在这种情况下,我并不担心分词,但在许多其他情况下我担心,所以我试图保持我的代码一致。但是,当我双引号该数组时,该命令失败。将代码简化为单个元素可得出以下结果:
bash-5.0# sha256sum ${filelist[0]} | head -c 64
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
bash-5.0# sha256sum "${filelist[0]}" | head -c 64
sha256sum: can't open 'file1
': No such file or directory
显然,我可以......不使用双引号,因为在这种情况下,分词不是问题。但我想发帖,因为将来可能会这样。
我的问题有两个部分:
- 除了上述双引号数组之外,是否有“最佳实践”方法来防止分词?
数组中的单引号来自哪里?编辑:没有单引号。单引号是显示无法打开的文件名的错误。
另外,出于好奇,为什么echo ${filelist[0]}
不包含额外的换行符却echo "${filelist[0]}"
包含呢?
答案1
引用数组扩展绝对没有问题。
当然,只要你知道并接受后果,不引用它也没有问题。任何未引用的扩展都会受到拆分和通配符的影响。并且,在您的代码中,${filelist[…]}
需要进行 IFS 字符删除(如果字符串包含任何<space>
、<tab>
或 ,则进行拆分<newline>
)。
这就是未加引号的扩展所做的事情,删除尾随的<newline>
.
什么创造这个问题是您在使用时readarray
没有从每个数组元素中删除尾随分隔符。这样做会保留反映在错误消息上的
尾随。<newline>
您可以使用的是:
readarray -t filelist < <(ls -A)
该-t
选项将删除每个文件名的所有尾随换行符。
-t 从读取的每行中删除尾部分隔符(默认换行符)。
但是您的代码还有一些其他问题。
无需声明或清空数组
filelist
。默认情况下由 readarray 完成。在其他一些情况下也需要这样做。不需要解析 的输出
ls
,事实上,这是一个坏主意。获取数组中的文件列表的最简单方法很简单:filelist=( ./* )
而且,为了让它变得更好,最好避免使用目录:
for file in ./*; do [[ -f $file ]] && filelist+=( "$file" ) done
在循环中,
$file
应使用 var 的值:for file in "${filelist[@]}"; do sha256sum "$file" | head -c 64 done
除非你使用
for file in "${!filelist[@]}"; do
which将列出键数组的。整个列表只需处理即可一调用 sha256sum:
sha256sum "${filelist[@]}" | cut -c -64
改进后的脚本是:
filelist=() # declare filelist as an array and empty it.
for file in ./*; do
if [[ -f $file ]]; then
filelist+=( "$file" )
fi
done
declare -r filelist # declare filelist as readonly.
sha256sum "${filelist[@]}" | cut -c -64
答案2
在这种情况下我不担心分词
嗯,事实上,你是依靠它从数组条目中删除尾随换行符!
巴什的readarray
( mapfile
)默认情况下保留分隔符。手册页或命令行帮助似乎没有明确说明,但有一个选项消除分隔符,因此默认情况下它不会被删除:
-t Remove a trailing delim (default newline) from each line read.
所以,数组中的实际字符串是。file1[newline]
如果没有引号,分词会删除尾随空格,从而修复换行符。但是,如果文件名中含有空格,分词就会像往常一样把它们弄乱。双引号数组可以防止这种情况。回答你的第一个问题,最佳实践是双引号,这里我们只是有一个不需要的额外换行符。
(双引号数组或是$@
一种稍微令人困惑的例外情况,其中双引号字符串会产生多个单词,每个单词对应一个数组元素。)
您也可以${filelist[$file]}
在sha256sum
命令行中使用。这是行不通的,file
已经包含从数组接收的值,而不是索引。
作为最小的修改,这可能会起作用:
declare -a filelist
readarray -t filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
sha256sum "$file" | head -c 64
done
(我认为明确的declare
实际上也没有必要。)
上述问题与此无关ls
上述问题与本身如果您将文件名存储在文件中,每行一个,并使用readarray
/mapfile
来读取它们而不使用该-t
选项,您也会遇到同样的问题。 (或者,如果您阅读了 的输出find
,但在这种情况下,您也许可以使用find -exec
它。)
当然,这个是无用的使用ls
和 的某些版本ls
可能会破坏输出中的文件名。 (我不认为 GNU ls 在输出到管道时会这样做。)
在 Bash 中,您可以用 glob 填充数组:
shopt -s dotglob
filelist=(*)
for file in *; do ...
或者只在 glob 上运行循环而不存储到数组:
shopt -s dotglob
for file in *; do ...
请注意,您确实需要shopt -s dotglob
要得到*
点文件,这取决于 shell。
答案3
基于您的代码片段的部分问题可能是您正在解析ls
.这是危险的并且充满了无数问题,最好避免。
而不是
declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
它更简单(也更安全!):
for file in *; do
在这种情况下:
for file in *; do
sha256sum "${file}" | head -c 64
done
readarray
当您调用它时,它也有助于保留传递给它的文字数据,包括换行符。因此,当您回显引用的值时,换行符将被保留。当您不引用它时,shell 将其作为令牌间空白来忽略。这也是sha256sum
失败的原因。如果您有一个名为 的文件foo
,readarray
则传递一个值foo\n
,该值与文件不对应。取消引用此“修复”问题,因为意外地丢弃了部分变量值。