我有以下脚本:
#!/bin/bash
KID3=$(command -v kid3-cli)
ARG1="-c 'get'"
file="'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'"
echo Command:
echo "$KID3" "$ARG1" "$file"
echo The kid3-cli output:
"$KID3" "$ARG1" "$file"
这会产生以下结果:
Command:
/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
The kid3-cli output:
-c 'get', '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac' does not exist
请注意kid3-cli 输出中的逗号。我尝试过使用荣誉而不是引号(即${KID3} ${ARG1} ${file}
),但这似乎没有任何区别。如果我使用多个命令变量($ARG1、$ARG2 等),则在 Kid3-cli 输出中每个变量后都会跟一个逗号。
当我将命令 ( /usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
) 复制并粘贴到交互式 bash shell 中(即在命令提示符下)时,它可以工作。
Kid3-cli 输出中的这些逗号从何而来,我缺少什么以及如何解决此问题?
工作命令
一个好的起点是了解这里发生的事情;那么就更容易看出在其他情况下会发生其他事情。
你的命令是:
shell 将命令拆分为单词,它使用未转义和未加引号的空格和制表符作为分隔符。单引号告诉 shell 这
get
是一个单词(即使没有引号,它也是一个单词),并且这1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
是一个单词(没有引号,它也不会是一个单词)。实际上这些话是:/usr/bin/kid3-cli
-c
get
1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
请注意,引号已完成其工作并已被删除。
然后 shell 运行
/usr/bin/kid3-cli
并给出三个参数:-c
、get
和1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
。kid3-cli
无法知道是否以及在何处使用了引号。显然,树参数是工具可以毫无错误地理解的元组。麻烦的命令
现在这个:
双引号非数组变量总是扩展为一个字(除非它是一个错误,例如由于
set -u
变量未设置)。这三个变量将扩展为恰好三个单词:/usr/bin/kid3-cli
从扩展的"$KID3"
-c 'get'
从扩展的"$ARG1"
'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
从扩展的"$file"
扩展变量中出现的引号并不特殊。当(如果!)您告诉 shell(或另一个 shell,或其他什么)再次解析结果时(例如
eval
:),它们可能会很特别。这里你不必也不需要告诉 shell 再次解析结果;此外,这样做是一种不好的做法。shell 运行
/usr/bin/kid3-cli
并给出两个参数:-c 'get'
和'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
。不仅-c
且get
都在一个参数内;单引号也到达kid3-cli
. 显然,这不是该工具可以按照您想要的方式理解的内容。关于什么
echo
?echo
通过逐字打印其所有参数(解释为选项的参数除外)来工作,每个非最后一个参数与下一个参数之间有一个空格字符;默认情况下加上一个尾随换行符。echo "$KID3" "$ARG1" "$file"
结果如下:echo
/usr/bin/kid3-cli
从扩展的"$KID3"
-c 'get'
从扩展的"$ARG1"
'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
从扩展的"$file"
shell 运行
echo
并给出三个参数:/usr/bin/kid3-cli
、-c 'get'
和'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
。你看:其中一些空格来自变量,其他空格来自
echo
。echo
请注意,如果get/usr/bin/kid3-cli
、-c
、'get'
、'1-01
、-
、Johann Strauss
、-
和__Waldmeister___ Ouverture.flac'
(8 个参数)或者echo
将所有内容都作为一个长参数,则输出将是相同的。如果您
echo
在工作命令前面添加,即如果您给它的参数是工作命令的单词,即如果您运行:那么输出将是:
再次没有任何指示添加了哪些空格
echo
。echo
不是“可视化”论证的好工具。我个人比较喜欢printf '<%s>\n'
。在你的情况下printf '<%s>\n' "$KID3" "$ARG1" "$file"
会打印:很明显,
-c 'get'
这是一个单一的参数,单引号必须是printf
。仍然存在歧义:如果一个参数是
/usr/bin/kid3-cli>\n<-c 'get'
(其中\n
此处表示换行符,但我的意思是参数内的文字printf
换行符),那么我会像单独的参数一样显示它:/usr/bin/kid3-cli
和-c 'get'
。尽管如此,内部争论>\n<
还是很少见(至少在我的工作流程中;<%s>\n
根据您的需要进行调整)。不加引号
$ARG1
如果您设置:
如果你运行:
那么这将起作用(使用默认值
$IFS
)。不加引号的$ARG1
将被分成两个单词:-c
和get
。请注意,如果您想kid3-cli
查看get
(而不是'get'
),那么您不能存储-c 'get'
在里面ARG1
。同样,file="'…'"
这里也是错误的。$ARG1
如果变量的值是静态的,完全在您的控制之下,没有触发文件名生成(通配符)的字符,那么不加引号并不是那么糟糕,这$IFS
会导致将值精确地分割到您想要的位置。一般来说,你应该总是引用。最主要的是 shell 变量用于存储数据,而不是存储 shell 代码。在
/usr/bin/kid3-cli -c 'get' …
您键入的内容中,单引号对于 shell 在语法上是有意义的,后面的空格也是如此-c
。这些部分是 shell 代码,将它们存储在变量中是错误的。有时(就像这里)您可以存储有意义的空间而不引用变量,但这仍然是一个不好的做法。这里的其他想法:我们如何运行存储在变量中的命令?
解决方案
(不是唯一的解决方案,请参阅已经链接的问题。一个可靠、安全、足够优雅的解决方案,适合您的代码结构。)
在 Bash 中,您可以使用数组来可靠地存储多个单词:
单引号包围
get
是不需要的,我只是想告诉你它们可能在那里。"${ARG1[@]}"
将扩展为两个词:-c
和get
。通过这种方法,您也可以只使用一个逐渐构建的数组:
(引号包含
1-01 … ….flac
很重要)。或者使用您一步构建的一个数组:在每种情况下,生成的命令将等效于:
除非您有充分的理由使用变量,否则您应该只运行上面的代码,甚至可能没有单词
command
。