何时用引号将 shell 变量括起来?

发布于 2025-01-13 21:03:29 字数 189 浏览 1 评论 0原文

我应该或不应该在 shell 脚本中用引号括住变量吗?

例如,以下内容是否正确:

xdg-open $URL
[ $? -eq 2 ]

xdg-open "$URL"
[ "$?" -eq "2" ]

如果是,为什么?

Should or should I not wrap quotes around variables in a shell script?

For example, is the following correct:

xdg-open $URL
[ $? -eq 2 ]

or

xdg-open "$URL"
[ "$?" -eq "2" ]

And if so, why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

临风闻羌笛 2025-01-20 21:03:29

一般规则:如果它可以为空或包含空格(或实际上任何空格)或特殊字符(通配符),则引用它。不使用空格引用字符串通常会导致 shell 将单个参数分解为多个参数。

$? 不需要引号,因为它是一个数值。 $URL 是否需要它取决于您允许的内容以及如果它为空您是否仍然需要参数。

我倾向于出于习惯总是引用字符串,因为这样更安全。

General rule: quote it if it can either be empty or contain spaces (or any whitespace really) or special characters (wildcards). Not quoting strings with spaces often leads to the shell breaking apart a single argument into many.

$? doesn't need quotes since it's a numeric value. Whether $URL needs it depends on what you allow in there and whether you still want an argument if it's empty.

I tend to always quote strings just out of habit since it's safer that way.

一花一树开 2025-01-20 21:03:29

简而言之,引用不需要 shell 执行分词和通配符扩展的所有内容。

单引号逐字保护它们之间的文本。当您需要确保外壳完全不接触琴弦时,它是合适的工具。通常,当您不需要变量插值时,它是选择的引用机制。

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

当需要变量插值时,双引号适用。通过适当的调整,当您在字符串中需要单引号时,这也是一个很好的解决方法。 (没有直接的方法来转义单引号之间的单引号,因为单引号内没有转义机制——如果有的话,它们不会完全逐字引用。)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

当您特别要求 shell 执行时,不适合使用引号分词和/或通配符扩展。

单词分割(又名标记分割);

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

相比之下:(

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

循环仅在单引号字符串上运行一次。)

 $ for word in '$words'; do echo "$word"; done
 $words

(循环仅在单引号字符串上运行一次。)

通配符扩展:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

相比之下:(

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

没有字面上名为 的文件file*.txt。)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(也没有名为 $pattern 的文件!)

更具体地说,任何包含文件名的内容通常都应该用引号引起来(因为文件名可以包含空格和其他内容) shell 元字符)。任何包含 URL 的内容通常都应该加引号(因为许多 URL 包含 shell 元字符,如 ?&)。任何包含正则表达式的内容通常都应该被引用(同上)。任何包含除非空白字符之间的单个空格之外的重要空白的内容都需要被引用(因为否则,shell 会将空白有效地合并为单个空格,并修剪任何前导或尾随空白)。

当您知道变量只能包含不包含 shell 元字符的值时,引号是可选的。因此,不带引号的 $? 基本上没问题,因为该变量只能包含单个数字。但是,"$?" 也是正确的,并且为了总体一致性和正确性而推荐(尽管这是我个人的建议,而不是广泛认可的策略)。

非变量的值基本上遵循相同的规则,尽管您也可以转义任何元字符而不是引用它们。举个常见的例子,一个带有 & 的 URL 会被 shell 解析为后台命令,除非该元字符被转义或加引号:(

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

当然,如果 URL 位于不带引号的变量。)对于静态字符串,单引号最有意义,尽管任何形式的引用或转义都可以在这里使用。

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

最后一个例子还提出了另一个有用的概念,我喜欢称之为“跷跷板引用”。如果需要混合使用单引号和双引号,可以将它们相邻使用。例如,以下带引号的字符串

'$HOME '
"isn't"
' where `<3'
"' is."

可以背对背粘贴在一起,在标记化和引号删除后形成单个长字符串。

nbsp;echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

这不是很清晰,但这是一种常见的技术,因此了解一下是很有必要的。

顺便说一句,脚本 通常不应使用 ls 进行任何操作。通配符,只需...使用它。

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(在后一个示例中,循环完全是多余的;printf 特别适用于多个参数。stat 也是如此。但是循环通配符匹配是一个常见问题,并且经常这样做)

包含要循环的标记列表或要扩展的通配符的变量不太常见,因此我们有时缩写为“引用所有内容,除非您确切知道自己在做什么”。


$*"$@" 之间的区别值得特别提及。简而言之,永远不要做

for i in $*

你的意思是写的

for i in "$@"

$*$@ 之间的区别在于,只有后者正确地保留参数列表中的引用; 但前提是你在它周围使用双引号。语法看起来很奇怪,但我猜最初的设计者在遇到裸 $* 的限制时就继承了它。

同样,如果 Bash 数组中有字符串,则正确的插值方法是使用带有双引号的 "${array[@]}"@

In short, quote everything where you do not require the shell to perform word splitting and wildcard expansion.

Single quotes protect the text between them verbatim. It is the proper tool when you need to ensure that the shell does not touch the string at all. Typically, it is the quoting mechanism of choice when you do not require variable interpolation.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Double quotes are suitable when variable interpolation is required. With suitable adaptations, it is also a good workaround when you need single quotes in the string. (There is no straightforward way to escape a single quote between single quotes, because there is no escape mechanism inside single quotes -- if there was, they would not quote completely verbatim.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

No quotes are suitable when you specifically require the shell to perform word splitting and/or wildcard expansion.

Word splitting (aka token splitting);

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

By contrast:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(The loop only runs once, over the single, quoted string.)

 $ for word in '$words'; do echo "$word"; done
 $words

(The loop only runs once, over the literal single-quoted string.)

Wildcard expansion:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

By contrast:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(There is no file named literally file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(There is no file named $pattern, either!)

In more concrete terms, anything containing a filename should usually be quoted (because filenames can contain whitespace and other shell metacharacters). Anything containing a URL should usually be quoted (because many URLs contain shell metacharacters like ? and &). Anything containing a regex should usually be quoted (ditto ditto). Anything containing significant whitespace other than single spaces between non-whitespace characters needs to be quoted (because otherwise, the shell will munge the whitespace into, effectively, single spaces, and trim any leading or trailing whitespace).

When you know that a variable can only contain a value which contains no shell metacharacters, quoting is optional. Thus, an unquoted $? is basically fine, because this variable can only ever contain a single number. However, "$?" is also correct, and recommended for general consistency and correctness (though this is my personal recommendation, not a widely recognized policy).

Values which are not variables basically follow the same rules, though you could then also escape any metacharacters instead of quoting them. For a common example, a URL with a & in it will be parsed by the shell as a background command unless the metacharacter is escaped or quoted:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Of course, this also happens if the URL is in an unquoted variable.) For a static string, single quotes make the most sense, although any form of quoting or escaping works here.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

The last example also suggests another useful concept, which I like to call "seesaw quoting". If you need to mix single and double quotes, you can use them adjacent to each other. For example, the following quoted strings

'$HOME '
"isn't"
' where `<3'
"' is."

can be pasted together back to back, forming a single long string after tokenization and quote removal.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

This isn't awfully legible, but it's a common technique and thus good to know.

As an aside, scripts should usually not use ls for anything. To expand a wildcard, just ... use it.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(The loop is completely superfluous in the latter example; printf specifically works fine with multiple arguments. stat too. But looping over a wildcard match is a common problem, and frequently done incorrectly.)

A variable containing a list of tokens to loop over or a wildcard to expand is less frequently seen, so we sometimes abbreviate to "quote everything unless you know precisely what you are doing".


The difference between $* and "$@" deserves a special mention. In short, never do

for i in $*

What you meant is written

for i in "$@"

The difference between $* and $@ is that only the latter correctly preserves quoting in the list of arguments; but only if you use double quotes around it. The syntax looks weird, but I guess the original designers just grandfathered it in when they bumped into the limitations of bare $*.

Similarly, if you have strings in a Bash array, the correct way to interpolate that is with "${array[@]}" with double quotes and @.

秋心╮凉 2025-01-20 21:03:29

以下是一般引号的三点公式:

双引号

在我们想要抑制分词和通配符的上下文中。同样,在我们希望将文字视为字符串而不是正则表达式的上下文中。

单引号

在字符串文字中,我们想要抑制反斜杠的插值和特殊处理。换句话说,使用双引号的情况是不合适的。

无引号

在我们绝对确定不存在分词或通配符问题或者我们确实想要分词和通配符的情况下。


示例

双引号

  • 带有空格的文字字符串("StackOverflow Rocks!""Steve's Apple"
  • 变量扩展( "$var", "${arr[@]}")
  • 命令替换 ("$(ls)", " `ls`")
  • 通配符所在目录路径或文件名部分包含空格 ("/my dir/"*)
  • 以保护单引号 ("single'quote'delimited'string")
  • Bash 参数扩展 (< code>"${filename##*/}")

单引号

  • 命令名称和参数中含有空格的
  • 文字字符串需要抑制插值('确实花费$$!', '只是一个反斜杠,后跟 at: \t''literal `backticks`')
  • 来保护双引号 ('The "crux"'; >)
  • 需要抑制插值的正则表达式文字
  • 对涉及特殊字符 ($'\n\t') 的文字使用 shell 引用
  • 在我们需要保护多个单引号和双引号的地方使用 shell 引用($'{"table": "users", "where": "first_name"=\'Steve\'}')

没有引号

  • 标准数字变量周围 (< code>$$、$?$# 等)
  • 在算术上下文中,如 ((count++))“${arr[idx]}”, "${string:start:length}"
  • 位于 [[ ]] 表达式内,不存在分词和通配符问题(这是一个风格问题,意见可能会有所不同广泛)
  • 我们想要分词(for $words)的地方
  • 我们想要通配(for txtfile in *.txt; do ...),
  • 我们希望 ~ 被解释为 $HOME (~/"some dir" 但不是 < code>"~/some dir")

另请参阅:

Here is a three-point formula for quotes in general:

Double quotes

In contexts where we want to suppress word splitting and globbing. Also in contexts where we want the literal to be treated as a string, not a regex.

Single quotes

In string literals where we want to suppress interpolation and special treatment of backslashes. In other words, situations where using double quotes would be inappropriate.

No quotes

In contexts where we are absolutely sure that there are no word splitting or globbing issues or we do want word splitting and globbing.


Examples

Double quotes

  • literal strings with whitespace ("StackOverflow rocks!", "Steve's Apple")
  • variable expansions ("$var", "${arr[@]}")
  • command substitutions ("$(ls)", "`ls`")
  • globs where directory path or file name part includes spaces ("/my dir/"*)
  • to protect single quotes ("single'quote'delimited'string")
  • Bash parameter expansion ("${filename##*/}")

Single quotes

  • command names and arguments that have whitespace in them
  • literal strings that need interpolation to be suppressed ('Really costs $$!', 'just a backslash followed by a t: \t'; 'literal `backticks`')
  • to protect double quotes ('The "crux"')
  • regex literals that need interpolation to be suppressed
  • use shell quoting for literals involving special characters ($'\n\t')
  • use shell quoting where we need to protect several single and double quotes ($'{"table": "users", "where": "first_name"=\'Steve\'}')

No quotes

  • around standard numeric variables ($$, $?, $# etc.)
  • in arithmetic contexts like ((count++)), "${arr[idx]}", "${string:start:length}"
  • inside [[ ]] expression which is free from word splitting and globbing issues (this is a matter of style and opinions can vary widely)
  • where we want word splitting (for word in $words)
  • where we want globbing (for txtfile in *.txt; do ...)
  • where we want ~ to be interpreted as $HOME (~/"some dir" but not "~/some dir")

See also:

失与倦" 2025-01-20 21:03:29

为了安全起见,我通常使用像 "$var" 这样的引号,除非我确定 $var 不包含空格。

我确实使用 $var 作为连接行的简单方法:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

I generally use quoted like "$var" for safe, unless I am sure that $var does not contain space.

I do use $var as a simple way to join lines:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文