Cou*_*ine 173 shell bash quoting command-substitution
在我的 Bash 环境中,我使用包含空格的变量,并在命令替换中使用这些变量。
引用我的变量的正确方法是什么?如果这些是嵌套的,我该怎么做?
DIRNAME=$(dirname "$FILE")
Run Code Online (Sandbox Code Playgroud)
或者我在替换之外引用?
DIRNAME="$(dirname $FILE)"
Run Code Online (Sandbox Code Playgroud)
或两者?
DIRNAME="$(dirname "$FILE")"
Run Code Online (Sandbox Code Playgroud)
还是我使用反引号?
DIRNAME=`dirname "$FILE"`
Run Code Online (Sandbox Code Playgroud)
这样做的正确方法是什么?以及如何轻松检查报价是否正确?
l0b*_*0b0 239
按照从最差到最好的顺序:
DIRNAME="$(dirname $FILE)" 如果$FILE包含空格(或$IFS当前包含的任何字符)或通配符,则不会执行您想要的操作\[?*。DIRNAME=`dirname "$FILE"`从技术上讲是正确的,但不建议将反引号用于命令扩展,因为嵌套它们时会增加额外的复杂性,并且会在其中进行额外的反斜杠处理。DIRNAME=$(dirname "$FILE")是正确的,但仅仅是因为这是对标量(不是数组)变量的赋值。如果您在任何其他上下文中使用命令替换,例如export DIRNAME=$(dirname "$FILE")或du $(dirname -- "$FILE"),如果扩展结果包含空格或通配符,则缺少引号会导致问题。DIRNAME="$(dirname "$FILE")"(除了缺少的--,见下文)是推荐的方式。您可以DIRNAME=在不更改任何其他内容的情况下用命令和空格替换,并dirname接收正确的字符串。进一步改进:
DIRNAME="$(dirname -- "$FILE")"如果$FILE以破折号开头则有效。DIRNAME="$(dirname -- "$FILE" && printf x)" && DIRNAME="${DIRNAME%?x}" || exit即使$FILE的 dirname 以换行符结尾也能工作,因为$()在输出结束时切断换行符,包括添加的换行符dirname和可能是实际数据一部分的换行符。您可以随心所欲地嵌套命令扩展。随着$()您始终创建一个新的引用上下文,因此您可以执行以下操作:
foo "$(bar "$(baz "$(ban "bla")")")"
Run Code Online (Sandbox Code Playgroud)
你没有想尝试与反引号。
Gra*_*eme 20
您始终可以使用 显示变量引用的效果printf。
分词完成于var1:
$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]
Run Code Online (Sandbox Code Playgroud)
var1 引用,所以没有分词:
$ printf '[%s]\n' "$var1"
[hello     world]
Run Code Online (Sandbox Code Playgroud)
分词在var1inside $(),相当于echo "hello" "world":
$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]
Run Code Online (Sandbox Code Playgroud)
没有分词var1,不引用没有问题$():
$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]
Run Code Online (Sandbox Code Playgroud)
var1再次分词:
$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]
Run Code Online (Sandbox Code Playgroud)
引用两者,最简单的方法来确定。
$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]
Run Code Online (Sandbox Code Playgroud)
不引用变量也会导致其内容的全局扩展:
$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]
Run Code Online (Sandbox Code Playgroud)
请注意,这仅在变量展开后发生。在赋值期间没有必要引用 glob:
$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]
Run Code Online (Sandbox Code Playgroud)
使用set -f禁用此行为:
$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]
Run Code Online (Sandbox Code Playgroud)
并set +f重新启用它:
$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Run Code Online (Sandbox Code Playgroud)
        小智 13
除了已接受的答案:
虽然我普遍同意@l0b0在这里的回答,但我怀疑在“最差到最好”列表中放置裸反引号至少部分是由于$(...)随处可用的假设。我意识到这个问题指定了 Bash,但有很多时候 Bash 的意思是/bin/sh,这实际上可能并不总是完整的 Bourne Again shell。
特别是,普通的 Bourne shell 不知道如何处理$(...),因此声称与其兼容的脚本(例如,通过#!/bin/shshebang 行)如果它们实际上是由“真实”运行的,则可能会出现错误行为/bin/sh——这是例如,在生成 init 脚本或打包前后脚本时特别感兴趣,并且可以在安装基本系统期间将其放置在一个令人惊讶的地方。
如果其中任何一个听起来像是您计划用这个变量做的事情,那么嵌套可能比让脚本实际、可预测地运行更重要。当它是一个足够简单的案例并且可移植性是一个问题时,即使我希望脚本通常在/bin/shBash 的系统上运行,我也经常出于这个原因倾向于使用反引号,多个分配而不是嵌套。
话虽如此,实现$(...)(Bash、Dash 等)的 shell 无处不在,这让我们在大多数情况下可以坚持使用更漂亮、更易于嵌套且最近更喜欢的 POSIX 语法,例如@l0b0 提到的所有原因。
旁白:这也偶尔出现在 StackOverflow 上——