Bash 括号快速参考

2025-05-26

Bash 括号快速参考

封面图片来源:Fonts.com

Bash 有很多不同类型的括号,非常多。不同的括号加倍使用会赋予其更多含义,而括号前面的美元符号则意味着更多不同的含义。而且,这些括号的用法与许多其他语言不同。由于我并非一直在编写 Bash 脚本,所以我经常会花 5 秒钟来寻找哪种括号更合适。所以,我打算把它们都整理出来,然后打印出来,用订书钉钉在我办公桌旁的墙上。或许还会配上一个装饰框。就这样,我们开始吧。

关于所有这些,需要注意的是,Bash 通常喜欢在圆括号或方括号及其内部内容之间看到空格。它不喜欢花括号中的空格。我们将按照总波浪线数(NTS 分数)的顺序进行介绍。

(单括号)

单括号会在子shell中运行其中的命令。这意味着它们会运行其中的所有命令,然后返回一个退出代码。任何声明的变量或环境变量的更改都将被清除并消失。由于它位于子shell中,因此如果将其放在循环中,运行速度会比不使用括号调用命令时稍慢一些。

a='This string'
( a=banana; mkdir $a )
echo $a
# => 'This string'
ls
# => ...
# => banana/
Enter fullscreen mode Exit fullscreen mode

((双括号))

这适用于整数运算。您可以在这些括号内执行赋值、逻辑运算以及乘法或取模等数学运算。但请注意,它们没有输出。括号内发生的任何变量更改都会保留,但不要期望能够将结果赋值给任何值。如果括号内的结果非零,则返回(成功)退出代码。如果括号内的结果为零,则返回1退出代码

i=4
(( i += 3 ))
echo $i
# => 7
(( 4 + 8 ))
# => No Output
echo $?  # Check the exit code of the last command
# => 0
(( 5 - 5 ))
echo $?
# => 1

# Strings inside get considered 'zero'.
(( i += POO ))
echo $i
# => 7

# You can't use it in an expression
a=(( 4 + 1 ))
# => bash: syntax error near unexpected token '('
Enter fullscreen mode Exit fullscreen mode

<(尖括号)

感谢Thomas H Jones II的评论,它启发了本节关于流程替代的内容

你错过了一个非常棒的 BASH 语法:<( COMMAND )。基本上,在子 shell 中运行命令,然后通过文件描述符返回其输出。这意味着你可以执行以下操作:

  • 两个流的差异
  • 在 shell 中运行命令来为需要以文件而不是流的形式输入的其他命令创建输入“文件”。

另一个很棒的功能是VAR=($( COMMAND ))...它获取输出COMMAND并从中创建一个数组变量。

当您关心命令如何退出而不是其输出时也很有用$( COMMAND )$?(例如,您想测试 grep 是否在文件中找到字符串,[[ $( grep -q PATTERN FILE )$? ]]

这被称为进程替换。它很像管道,只不过你可以在任何需要文件参数的命令中使用它。而且你可以一次使用多个!

sort -nr -k 5 <( ls -l /bin ) <( ls -l /usr/bin ) <( ls -l /sbin )

# => Like a billion lines of output that contain many of the
# => executables on your computer, sorted in order of descending size.

# Just in case you don't magically remember all bash flags,
# -nr  means sort numerically in reverse (descending) order
# -k 5 means sort by Kolumn 5.  In this case, for `ls -l`, that is the "file size"
Enter fullscreen mode Exit fullscreen mode

之所以能成功,是因为 sort 命令需要一个或多个文件名作为参数。在后台,它<( stuff )实际上会输出一个临时文件(未命名管道文件)的名称,供sort命令使用。

另一个例子是使用comm命令,它会找出文件中相同的行。由于comm需要对输入文件进行排序,你可以这样做:

# The lame way
sort file1 > file1.sorted
sort file2 > file2.sorted
comm -12 file1.sorted file2.sorted
Enter fullscreen mode Exit fullscreen mode

哦,你可以完全成为一个 BAshMF 并这样做:

# The baller way
comm -12 <( sort file1 ) <( sort file2 )
Enter fullscreen mode Exit fullscreen mode

$( 美元单括号 )

这用于将子shell命令的输出插入到字符串中。里面的命令会在子shell中运行,然后所有输出都会被放入你正在构建的字符串中。

intro="My name is $( whoami )"
echo $intro
# => My name is ryan

# And just to prove that it's a subshell...
a=5
b=$( a=1000; echo $a )
echo $b
# => 1000
echo $a
# => 5
Enter fullscreen mode Exit fullscreen mode

$( 美元 单括号 美元 Q )$?

再次向托马斯致敬,感谢他的提示!

如果您想要插入一个命令,但只插入退出代码而不是值,那么您可以使用这个。

if [[ $( grep -q PATTERN FILE )$? ]]
then
  echo "Dat pattern was totally in dat file!"
else
  echo "NOPE."
fi
Enter fullscreen mode Exit fullscreen mode

虽然,实际上,与其说这是一种特殊的括号模式,不如说是一种有趣的用法,因为即使$?之间有空格,上面的代码也能正常工作。但无论如何,这仍然是一个巧妙的技巧。$( stuff )$?

$(( 美元双括号 ))

还记得常规的(( 双括号 ))不会输出任何内容吗?还记得它有多烦人吗?好吧,你可以使用$(( 美元双括号 ))来执行算术插值,这只是一种更贴切的说法,“将输出结果放入此字符串中”。

a=$(( 16 + 2 ))
message="I don't want to brag, but I have like $(( a / 2 )) friends."
echo $message
# => I don't want to brag, but I have like 9 friends."

b=$(( a *= 2 ))         # You can even do assignments.  The last value calculated will be the output.
echo $b
# => 36
echo $a
# => 36
Enter fullscreen mode Exit fullscreen mode

需要记住的是,这严格来说是整数运算,不能有小数。bc浮点运算请参考相关内容。

echo $(( 9 / 2 ))  # You might expect 4.5
# => 4

echo $(( 9 / 2.5 ))
# => bash: 9 / 2.5 : syntax error: invalid arithmetic operator (error token is ".5 ")
Enter fullscreen mode Exit fullscreen mode

[ 单方括号 ]

这是内置的 的替代版本test。运行其中的命令并检查其“真值”。长度为零的字符串为假。长度为一或一以上的字符串(即使这些字符是空格)为真。

这里列出了您可以执行的所有与文件相关的测试,例如检查文件是否存在或是否为目录。

这里列出了您可以执行的所有与字符串和整数相关的测试,例如检查两个字符串是否相等或一个字符串是否为零长度,或者一个数字是否大于另一个数字。

if [ -f my_friends.txt ]
then
    echo "I'm so loved!"
else
    echo "I'm so alone."
fi
Enter fullscreen mode Exit fullscreen mode

最后需要注意的是,test[实际上是 shell 命令。[[ ]]实际上是shell 语言本身的一部分。这意味着双方括号内的内容不会被视为参数。使用单方括号的原因是,如果您需要进行分词文件名扩展

下面我们来说明一下两者的区别。假设你以如下方式使用了双方括号。

[[ -f *.txt ]]
echo $?
# => 1
Enter fullscreen mode Exit fullscreen mode

错误,没有明确名为“[asterisk].txt”的文件。假设当前.txt目录中没有任何文件。

# If there's no files .txt files:
[ -f *.txt ]; echo $?
# => 1
Enter fullscreen mode Exit fullscreen mode

*.txt扩展为一个空字符串,它不是一个文件,然后测试就会被执行。让我们创建一个 txt 文件。

touch cool_beans.txt
# Now there's exactly one .txt file
[ -f *.txt ]; echo $?
# => 0
Enter fullscreen mode Exit fullscreen mode

*.txt扩展为以空格分隔的匹配文件名列表:“cool_beans.txt”,然后测试将使用该参数进行评估。由于文件存在,测试通过。但如果有两个文件呢?

touch i_smell_trouble.txt  # bean pun.  #sorrynotsorry
# Now there's two files
[ -f *.txt ]
# => bash: [: too many arguments.
Enter fullscreen mode Exit fullscreen mode

*.txt扩展为“cool_beans.txt i_smell_trouble.txt”,然后对测试进行评估。Bash 将每个文件名都视为一个参数,并接收了 3 个参数(而不是预期的两个),然后就模糊了。

只是为了强调我的观点:即使目前有两个.txt文件,下一个测试仍然会失败。

[[ -f *.txt ]]; echo $?
# => 1.  There is still no file called *.txt
Enter fullscreen mode Exit fullscreen mode

我试图举出一些例子来解释为什么你会想要这个,但我无法举出现实的例子。

大多数情况下,一个好的经验法则是:如果你需要使用test[ ],你自然会知道。如果你不确定是否需要,那么你可能不需要它,并且应该使用[[ 双方括号 ]]来避免该命令的许多棘手问题test。前提是你的 Shell 足够先进,可以支持它们。

[[双方括号]]

真/假测试。请阅读上文,了解单方括号和双方括号之间的区别。此外,双方括号支持扩展正则表达式匹配。使用引号将第二个参数括起来,可以强制执行原始匹配,而不是正则表达式匹配。

pie=good
[[ $pie =~ d ]]; echo $?
# => 0, it matches the regex!

[[ $pie =~ [aeiou]d ]]; echo $?
# => 0, still matches

[[ $pie =~ [aei]d ]]; echo $?
# => 1, no match

[[ $pie =~ "[aeiou]d" ]]; echo $?
# => 1, no match because there's no literal '[aeoiu]d' inside the word "good"
Enter fullscreen mode Exit fullscreen mode

另外,在双方括号内,<>您的语言环境排序。在单方括号内,按您机器的排序顺序排序,通常是 ASCII。

{ 单花括号 }

单花括号用于扩展。

echo h{a,e,i,o,u}p
# => hap hep hip hop hup
echo "I am "{cool,great,awesome}
# => I am cool I am great I am awesome

mv friends.txt{,.bak}
# => braces are expanded first, so the command is `mv friends.txt friends.txt.bak`
Enter fullscreen mode Exit fullscreen mode

更酷的是,你还可以创建范围!而且带前导零!

echo {01..10}
01 02 03 04 05 06 07 08 09 10
echo {01..10..3}
01 04 07 10
Enter fullscreen mode Exit fullscreen mode

${美元括号}

注意,内容周围没有空格。这是为了变量插值。当普通的字符串插值变得奇怪时,可以使用它。

# I want to say 'bananaification'
fruit=banana
echo $fruitification
# => "" No output, because $fruitification is not a variable.
echo ${fruit}ification
# => bananaification
Enter fullscreen mode Exit fullscreen mode

${美元括号} 的另一个用途是变量操作。以下是一些常见的用法。

如果变量未定义,则使用默认值。

function hello() {
  echo "Hello, ${1:-World}!"
}
hello Ryan
# => Hello Ryan!
hello
# => Hello World!

function sign_in() {
    name=$1
  echo "Signing in as ${name:-$( whoami )}"
}
sign_in
# => Signing in as ryan
sign_in coolguy
# => Signing in as coolguy
Enter fullscreen mode Exit fullscreen mode

获取变量的长度。

name="Ryan"
echo "Your name is ${#name} letters long!"
# => Your name is 4 letters long!
Enter fullscreen mode Exit fullscreen mode

切下符合图案的碎片。

url=https://assertnotmagic.com/about
echo ${url#*/}     # Remove from the front, matching the pattern */, non-greedy
# => /assertnotmagic.com/about
echo ${url##*/}    # Same, but greedy
# => about
echo ${url%/*}     # Remove from the back, matching the pattern /*, non-greedy
# => https://assertnotmagic.com
echo ${url%%/*}    # Same, but greedy
# => https:
Enter fullscreen mode Exit fullscreen mode

感谢Sanket Patel修复错误!

您可以将匹配的字母大写!

echo ${url^^a}
# => https://AssertnotmAgic.com/About
Enter fullscreen mode Exit fullscreen mode

您可以获得字符串切片。

echo ${url:2:5}  # the pattern is ${var:start:len}.  Start is zero-based.
# => tps://
Enter fullscreen mode Exit fullscreen mode

您可以替换图案。

echo ${url/https/ftp}
# => ftp://assertnotmagic.com

# Use a double slash for the first slash to do a global replace
echo ${url//[aeiou]/X}
# => https://XssXrtnXtmXgXc.cXm
Enter fullscreen mode Exit fullscreen mode

并且,您可以间接使用变量作为其他变量的名称

function grades() {
  name=$1
  alice=A
  beth=B
  charles=C
  doofus=D
  echo ${!name}
}

grades alice
# => A
grades doofus
# => D
grades "Valoth the Unforgiving"
# => bash: : bad substitution.   
# There is no variable called Valoth the Unforgiving,
# so it defaults to a blank value.  
# Then, bash looks for a variable with a name of "" and errors out.
Enter fullscreen mode Exit fullscreen mode

<<双角Heredocs

这就是在 Bash 中创建多行字符串的方法(一种方法)。两个箭头加上一个单词——任意一个你选择的单词——来表示字符串的开始。直到你重复这个神奇的单词,字符串才会结束。

read -r -d '' nice_message <<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE

echo "$nice_message"
# => Hi there!  I really like the way you look
# => when you are teaching newbies things
# => with empathy and compassion!
# => You rock!

# Note, if you store a string with newlines in a variable, 
# you have to quote it to get the newlines to actually print.
# If you just use `echo $nice_message`, it won't reflect newlines.
Enter fullscreen mode Exit fullscreen mode

这个词可以随便用。我一般用“HEREDOC”来方便以后使用。

评论区中 Ian Kirker 的真正古老智慧:

关于 HEREDOC 位有几点:

  • HEREDOC 与命令一起使用时,会被输入stdin到命令中。你在第一个示例中使用它来赋值变量的方式不起作用,至少对我的 bash 4.4.19 来说是这样。
$ nice_message=<<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
$ echo $nice_message

$
Enter fullscreen mode Exit fullscreen mode
$ cat <<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
$ 
Enter fullscreen mode Exit fullscreen mode
  • 如果在初始标记两边加上单引号,HEREDOC 将被视为单引号字符串,变量表达式不会被插入。
$ cat <<EOF
$RANDOM
EOF
4578

$ cat <<'EOF'
$RANDOM
EOF
$RANDOM
Enter fullscreen mode Exit fullscreen mode
  • 您还可以使用不带标签的三个小于符号来仅输入一个简单的字符串,如下所示:
$ cat <<EOF
beep
EOF
beep

$ cat <<<beep
beep
$
Enter fullscreen mode Exit fullscreen mode

最后一个技巧是,如果在箭头后添加破折号,它会抑制heredoc 中的任何前导制表符(但不是空格)。

cat <<-HEREDOC
        two leading tabs
    one leading tab
  two spaces here
HEREDOC

# => two leading tabs
# => one leading tab
# =>   two spaces here
Enter fullscreen mode Exit fullscreen mode

标点符号是杀手

希望以上内容对您有所帮助。如果您发现我遗漏了什么,或者您发现了其他更酷的用法,请务必告诉我,我会更新并公开表扬您的才华。感谢您的阅读!

文章来源:https://dev.to/rpalo/bash-brackets-quick-reference-4eh6
PREV
我向 DEV 咨询了简历建议,以下是我学到的 14 件事
NEXT
使用 React 构建 web3 前端