Bash 通配符和参数传递

Bash 通配符和参数传递

我有以下简化的 bash 脚本

#!/bin/bash

files=("$@")

if [ "X$files" = "X" ]; then
  files=$HOME/print/*.pdf;
fi

for file in "${files[@]}"; do
  ls "$file";
done

如果我将参数(文件名)作为参数传递,此脚本将打印正确的文件名。另一方面,如果我不传递参数,它将打印

/home/user/print/*.pdf: No such file or directory

为什么在这种情况下文件名没有扩展,如何修复?请注意,我使用files=("$@")and"${files[@]}"结构是因为我读到它比通常的“files=$*”更受青睐。

答案1

您指定files标量变量而不是大批多变的。

 files=$HOME/print/*.pdf

你正在分配一些细绳/home/highsciguy/print/*.pdf就像$files标量(又名字符串)变量一样。

使用:

files=(~/print/*.pdf)

或者

files=("$HOME"/print/*.pdf)

反而。 shell 会将该通配模式扩展为文件路径列表,并将每个文件路径分配给$files 大批

glob 的扩展是在赋值时完成的。

您不必使用非标准 sh 功能,并且您可以通过编写它来使用您的系统sh而不是这里:bash

#!/bin/sh -

[ "$#" -gt 0 ] || set -- ~/print/*.pdf

for file do
  ls -d -- "$file"
done

set是分配"$@"位置参数数组。

另一种方法可能是存储通配模式在标量变量中:

files=$HOME/print/*.pdf

并让 shell 在$files 变量扩展时扩展 glob。

IFS= # disable word splitting
for file in $files; do ...

在这里,因为$files没有引用(通常不应该这样做),所以它的扩展受到分词(我们在这里禁用)和通配符/文件名生成的影响。

因此*.pdf 将扩展到匹配文件列表。但是,如果$HOME包含通配符,它​​们也可以扩展,这就是为什么仍然最好使用数组变量。

答案2

您可能在没有数组的旧 shell 中看到过类似files=$*和的东西files=~/print/*.pdf,然后是ls $files

不在双引号内的变量替换将变量的值解释为以空格分隔的 shell 通配符模式列表,这些通配符模式将被替换为匹配的文件名(如果有)。例如, after files=~/print/*.pdf,ls $files扩展为ls与参数/home/highsciguy/print/bar.pdf,/home/highsciguy/print/foo.pdf等类似的内容。在这种情况下files=$*,此赋值将传递给脚本的参数连接起来,中间有空格,然后ls $files将它们拆分回来。

如果您的文件名包含空格或通配符,所有这些都会失败,这就是为什么您不应该这样做。请改用数组。

files=("$@")
if ((${#files[@]} == 0)); then
  files=("$HOME"/print/*.pdf)
fi

注意

  • 所有数组赋值都需要将数组值括在括号中:var=(…)
  • 要测试数组是否为空,请检查其长度。当是索引 0 的元素未设置的数组或空字符串"$files"时为空。files还有[ "X$foo" = "X" ]一种过时的方法来测试是否$foo为空:所有现代 shell 都能[ -n "$foo" ]正确实现。在 bash 中,您可以使用[[ -n $foo ]].

在不支持数组的 shell 中,实际上只有一个数组:shell 或当前函数的位置参数。在这里,您实际上并不需要数组files,事实上使用位置参数会更容易。

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do …

相关内容