Bash 函数参数 - 文件名中的空格

Bash 函数参数 - 文件名中的空格

我知道引用参数有规则,但无论我尝试什么,我似乎都不知道如何使其工作。

我有一个函数,它接受单个文件名作为参数,然后使用该文件名构建命令行并运行命令行。当任何文件中没有空格时,我能够使其工作,但是当引入带有空格的文件名时,它就会中断。我尝试让它与空格一起工作的所有方法要么不起作用,要么导致它也无法在没有空格的文件上起作用。

这是一个不带空格的示例脚本:

#!/bin/bash
list_files() {
    file="${1}"
    cmd="cat ${file}"
    echo "Running command: '${cmd}'"
    ${cmd}
}

TARGET_DIR="${1}"

while IFS= read -d $'\0' -r file; do
    list_files "${file}"
done < <(find ${TARET_DIR} -type f -print 0)

我刚刚创建了一个目录结构如下:

./files/
|-- one                # ==> Contains the text "files/one"
|-- two                # ==> Contains the text "files/two"
|-- three and four     # ==> Contains the text "files/three and four"
|-- five               # ==> Contains the text "files/five"

当我在上面的目录上运行上面的脚本时,我得到以下输出:

bash-3.2$ ./test.bash ./files
Running Command: 'cat ./files/one'
files/one
Running Command: 'cat ./files/two'
files/two
Running Command: 'cat ./files/three and four'
cat: ./files/three: No such file or directory
cat: and: No such file or directory
cat: four: No such file or directory
Running Command: 'cat ./files/five'
files/five

我做错了什么?无论文件名是什么,我怎样才能使这样的脚本正常工作?

注意:对于这个例子,它很简单,首先不将命令存储在变量中,只需打印它,然后通过键入命令本身两次来调用它,但对于我的真实脚本,命令要复杂得多,我想如果将来需要更改,只需将其维护在一处。

答案1

代替:

cmd="cat ${file}"
printf '%s\n' "Running command: '${cmd}'"
${cmd}

要么传递一个固定命令以eval将其解释为 shell 代码。

cmd='cat -- "$file"'
printf '%s\n' "Running command: '${cmd}'"
eval "$cmd"

这将输出Running command: cat -- "$file"可能不是你想要的。

或者(bash具体)使用正确printf %q引用$file,以便将其扩展传递给eval

printf -v cmd 'cat -- %q' "$file"
printf '%s\n' "Running command: $cmd"
eval "$cmd"

或者,由于这里是一个简单的命令,因此您可以将该简单命令的参数存储在数组中:

cmd=(cat -- "$file")
printf 'Running command: '
printf ' %q' "${cmd[@]}"
printf '\n'
"${cmd[@]}"

如果您知道$file保证不包含类似字符的特定字符:(请注意,:尽管这是 Unix 上文件名中完全有效的字符),您可以这样做:

cmd="cat:--:$file"
IFS=:  # split on :
set -f # disable glob
# use the split+glob operator by leaving the variable unquoted:
printf 'Running command: '
printf ' %q' $cmd
printf '\n'
$cmd

现在,在这种特定情况下,您还可以执行以下操作:

(PS4='Running command: '
set -x; cat -- "$file")

(注意消息会发送到 stderr)。

甚至:

PS4='Running command: ' find "$TARGET_DIR" -type f -exec sh -xc '
  cat "$1"' sh {} \;

或者为了避免运行那么多的shandcat命令,请使用ksh93wherecat是映射到 /opt/ast/bin/cat 的内置函数,并使用{} +语法来运行尽可能少的 shell:

PS4='Running command: ' find "$TARGET_DIR" -type f -exec ksh93 -c '
  PATH=/opt/ast/bin; set -x; for file do cat "$file"; done' ksh93 {} +

要显示带有标题的文件内容并运行尽可能少的命令,在 GNU 系统上,您还可以使用head

$ find "$TARGET_DIR" -type f -exec head -vc-0 {} +
==> ./file 1 <==
1
2

==> ./file 2 <==
test

zsh

zmodload zsh/mapfile
for file (**/*(D.)) printf '==> %q <==\n%s\n' $file $mapfile[$file]

相关内容