关于 bash 命令与变量替换的混淆

关于 bash 命令与变量替换的混淆

自 1989 年以来,我一直在命令行上编写一行 bash 脚本。这些脚本通常具有以下形式:

for name in 1 2 3; do wc $name.txt; done

现在我尝试使用 GIMP 进行一些图像处理,使用一个采用两个文件名作为参数的脚本。这是给我一个有效的命令行的东西:

/Applications/GIMP.app/Contents/MacOS/GIMP -b '(script-fu-overlay "png0004.tif" "png0000.tif") (script-fu-overlay "png0004.tif" "png0001.tif") (script-fu-overlay "png0004.tif" "png0002.tif") (script-fu-overlay "png0004.tif" "png0003.tif")'

它是在一个典型的单行代码的帮助下生成的:

X=\'$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)\'

但是......这是我的问题:

echo /Applications/GIMP.app/Contents/MacOS/GIMP -b $X

给了我上面的确切命令行,但是当我运行时

/Applications/GIMP.app/Contents/MacOS/GIMP -b $X

我收到有关无法打开带有引号的文件名的错误,但是当我运行时

/Applications/GIMP.app/Contents/MacOS/GIMP -b "$X"

一切正常。我真正的目标是了解我是否可以在完全不使用变量的情况下编写一行语句。就像是:

/Applications/GIMP.app/Contents/MacOS/GIMP -b \'$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)\'

但这显然以同样的方式失败了

/Applications/GIMP.app/Contents/MacOS/GIMP -b $X

失败。

答案1

是的,您可以在命令行上执行此操作,而无需求助于 shell 变量。

/Applications/GIMP.app/Contents/MacOS/GIMP -b "$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)"

请注意,我们需要取消转义的单引号,因为我们eval在这里不做。唯一需要的是命令扩展 $(...) 周围的双引号,以防止发生单词拆分,并将结果作为一个参数传递给gimp.

答案2

那这个呢:

$ /Applications/GIMP.app/Contents/MacOS/GIMP -b \
"$(printf '%s\n' '(script-fu-overlay "png0004.tif" "png'{0..0004}'.png")')"

答案3

man bash说:

展开的顺序是:大括号展开;波形符扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和路径名扩展 [...] 执行这些扩展后,原始单词中存在的引号字符将被删除,除非它们本身已被引用(引号删除)。

因此,仅引用从原词被删除。但你的那些引言只有在扩展之后才会出现,并且不会被删除。

在给定的情况下,您可以将它们保留,因为您的文件名不需要任何引号,但是您会遇到带有空格或其他特殊字符的文件名的问题。引号和反斜杠都不会帮助您,因为在此处理阶段它们不被视为引号。

编辑以满足“一步”请求:

但是,如果您有一堆带有空格(例如 和 )的文件foo file foo并且bar file bar想要cat其中一些怎么办?

你的命令行将显示

cat "foo file foo" "bar file bar"

但如果有更多,您将需要编写脚本。

echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done

将准确生成您要输入的行,但是

$(echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done)

将因上述原因而失败。将所有内容都用双引号括起来(对您的情况有帮助gimp)也不起作用,因为新命令需要用单词分隔,但不能在引号内分隔。

那么如何才能使引用像直接在命令行中输入一样工作呢?将字符串作为命令传递给 shell:

bash -c "$(echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done)"

这应该是单行执行生成命令的最通用方法。

相关内容