我有以下脚本:
#!/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 输出中的逗号。我尝试过使用 accolades 而不是引号(即${KID3} ${ARG1} ${file}
),但这似乎没有任何区别。如果我使用多个命令变量 ($ARG1、$ARG2 等),kid3-cli 输出中的每个变量后面都会有一个逗号。
当我将命令(/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
)复制并粘贴到交互式 bash shell(即在命令提示符下)中时,它可以起作用。
kid3-cli 输出中的这些逗号从何而来,我遗漏了什么,我该怎么做才能解决这个问题?
答案1
工作命令
当我将命令(
/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
)复制并粘贴到交互式 bash shell(即在命令提示符下)中时,它可以起作用。
一个好的起点是了解这里发生了什么;然后更容易看出在其他情况下会发生其他事情。
你的命令是:
/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
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
无法知道是否以及在何处使用了引号。显然,三参数是一个元组,该工具可以理解而不会出错。
麻烦的命令
现在这个:
"$KID3" "$ARG1" "$file"
双引号中的非数组变量始终扩展为一个单词(除非出现错误,例如由于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'
。你会看到:
/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
其中一些空格来自变量,其他空格来自。请注意,如果获得、、、、、、和(8 个参数)或将所有内容作为一个长参数,则输出将echo
相同。echo
/usr/bin/kid3-cli
-c
'get'
'1-01
-
Johann Strauss
-
__Waldmeister___ Ouverture.flac'
echo
如果您echo
在工作命令前添加参数,即如果您为其提供工作命令的单词参数,即如果您运行:
echo /usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
那么输出将是:
/usr/bin/kid3-cli -c get 1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
再次没有任何迹象表明哪些空格是由 添加的echo
。
echo
并不是“可视化”参数的好工具。我个人更喜欢printf '<%s>\n'
。在你的情况下printf '<%s>\n' "$KID3" "$ARG1" "$file"
会打印:
</usr/bin/kid3-cli>
<-c 'get'>
<'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'>
并且它会非常清楚-c 'get'
是一个单一的参数并且单引号得到了printf
。
仍然存在歧义:如果一个参数是/usr/bin/kid3-cli>\n<-c 'get'
(\n
这里表示换行符,但我的意思是文字参数内部有换行符),那么我的printf
会将其显示为单独的参数:/usr/bin/kid3-cli
和-c 'get'
。不过,带有>\n<
内部的参数很少见(至少在我的工作流程中如此;请<%s>\n
根据您的需要进行调整)。
未引用$ARG1
如果您设置:
ARG1="-c get"
file="1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac"
如果你运行:
"$KID3" $ARG1 "$file"
那么这将有效(使用默认的$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 中,你可以使用数组来可靠地存储多个单词:
KID3=$(command -v kid3-cli)
ARG1=( -c 'get' )
file="1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac"
"$KID3" "${ARG1[@]}" "$file"
单引号包围get
是不需要的,我只是想向你展示它们可能be there."${ARG1[@]}"
会扩展为两个词:-c
和get
。
通过这种方法,您也可以只使用一个逐步构建的数组:
cmnd=( "$(command -v kid3-cli)" )
cmnd+=( -c get )
cmnd+=( '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac' )
"${cmnd[@]}"
(引号很1-01 … ….flac
重要)。或者使用一步构建的一个数组:
cmnd=( "$(command -v kid3-cli)" -c get '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac' )
"${cmnd[@]}"
在每种情况下,结果命令都等同于:
command kid3-cli -c get "1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac"
除非您有充分的理由使用变量,否则您应该只运行上述操作,甚至可能不带单词command
。