Bash 括号快速参考
封面图片来源:Fonts.com
Bash 有很多不同类型的括号,非常多。不同的括号加倍使用会赋予其更多含义,而括号前面的美元符号则意味着更多不同的含义。而且,这些括号的用法与许多其他语言不同。由于我并非一直在编写 Bash 脚本,所以我经常会花 5 秒钟来寻找哪种括号更合适。所以,我打算把它们都整理出来,然后打印出来,用订书钉钉在我办公桌旁的墙上。或许还会配上一个装饰框。就这样,我们开始吧。
关于所有这些,需要注意的是,Bash 通常喜欢在圆括号或方括号及其内部内容之间看到空格。它不喜欢花括号中的空格。我们将按照总波浪线数(NTS 分数)的顺序进行介绍。
(单括号)
单括号会在子shell中运行其中的命令。这意味着它们会运行其中的所有命令,然后返回一个退出代码。任何声明的变量或环境变量的更改都将被清除并消失。由于它位于子shell中,因此如果将其放在循环中,运行速度会比不使用括号调用命令时稍慢一些。
a='This string'
( a=banana; mkdir $a )
echo $a
# => 'This string'
ls
# => ...
# => banana/
((双括号))
这适用于整数运算。您可以在这些括号内执行赋值、逻辑运算以及乘法或取模等数学运算。但请注意,它们没有输出。括号内发生的任何变量更改都会保留,但不要期望能够将结果赋值给任何值。如果括号内的结果非零,则返回零(成功)退出代码。如果括号内的结果为零,则返回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 '('
<(尖括号)
感谢Thomas H Jones II的评论,它启发了本节关于流程替代的内容
这被称为进程替换。它很像管道,只不过你可以在任何需要文件参数的命令中使用它。而且你可以一次使用多个!
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"
之所以能成功,是因为 sort 命令需要一个或多个文件名作为参数。在后台,它<( stuff )
实际上会输出一个临时文件(未命名管道文件)的名称,供sort
命令使用。
另一个例子是使用comm
命令,它会找出文件中相同的行。由于comm
需要对输入文件进行排序,你可以这样做:
# The lame way
sort file1 > file1.sorted
sort file2 > file2.sorted
comm -12 file1.sorted file2.sorted
哦,你可以完全成为一个 BAshMF 并这样做:
# The baller way
comm -12 <( sort file1 ) <( sort file2 )
$( 美元单括号 )
这用于将子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
$( 美元 单括号 美元 Q )$?
再次向托马斯致敬,感谢他的提示!
如果您想要插入一个命令,但只插入退出代码而不是值,那么您可以使用这个。
if [[ $( grep -q PATTERN FILE )$? ]]
then
echo "Dat pattern was totally in dat file!"
else
echo "NOPE."
fi
虽然,实际上,与其说这是一种特殊的括号模式,不如说是一种有趣的用法,因为即使和$?
之间有空格,上面的代码也能正常工作。但无论如何,这仍然是一个巧妙的技巧。$( 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
需要记住的是,这严格来说是整数运算,不能有小数。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 ")
[ 单方括号 ]
这是内置的 的替代版本test
。运行其中的命令并检查其“真值”。长度为零的字符串为假。长度为一或一以上的字符串(即使这些字符是空格)为真。
这里列出了您可以执行的所有与文件相关的测试,例如检查文件是否存在或是否为目录。
这里列出了您可以执行的所有与字符串和整数相关的测试,例如检查两个字符串是否相等或一个字符串是否为零长度,或者一个数字是否大于另一个数字。
if [ -f my_friends.txt ]
then
echo "I'm so loved!"
else
echo "I'm so alone."
fi
最后需要注意的是,test
和[
实际上是 shell 命令。[[ ]]
实际上是shell 语言本身的一部分。这意味着双方括号内的内容不会被视为参数。使用单方括号的原因是,如果您需要进行分词或文件名扩展。
下面我们来说明一下两者的区别。假设你以如下方式使用了双方括号。
[[ -f *.txt ]]
echo $?
# => 1
错误,没有明确名为“[asterisk].txt”的文件。假设当前.txt
目录中没有任何文件。
# If there's no files .txt files:
[ -f *.txt ]; echo $?
# => 1
*.txt
扩展为一个空字符串,它不是一个文件,然后测试就会被执行。让我们创建一个 txt 文件。
touch cool_beans.txt
# Now there's exactly one .txt file
[ -f *.txt ]; echo $?
# => 0
*.txt
扩展为以空格分隔的匹配文件名列表:“cool_beans.txt”,然后测试将使用该参数进行评估。由于文件存在,测试通过。但如果有两个文件呢?
touch i_smell_trouble.txt # bean pun. #sorrynotsorry
# Now there's two files
[ -f *.txt ]
# => bash: [: too many arguments.
*.txt
扩展为“cool_beans.txt i_smell_trouble.txt”,然后对测试进行评估。Bash 将每个文件名都视为一个参数,并接收了 3 个参数(而不是预期的两个),然后就模糊了。
只是为了强调我的观点:即使目前有两个.txt
文件,下一个测试仍然会失败。
[[ -f *.txt ]]; echo $?
# => 1. There is still no file called *.txt
我试图举出一些例子来解释为什么你会想要这个,但我无法举出现实的例子。
大多数情况下,一个好的经验法则是:如果你需要使用
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"
另外,在双方括号内,<
按>
您的语言环境排序。在单方括号内,按您机器的排序顺序排序,通常是 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`
更酷的是,你还可以创建范围!而且带前导零!
echo {01..10}
01 02 03 04 05 06 07 08 09 10
echo {01..10..3}
01 04 07 10
${美元括号}
注意,内容周围没有空格。这是为了变量插值。当普通的字符串插值变得奇怪时,可以使用它。
# I want to say 'bananaification'
fruit=banana
echo $fruitification
# => "" No output, because $fruitification is not a variable.
echo ${fruit}ification
# => bananaification
${美元括号} 的另一个用途是变量操作。以下是一些常见的用法。
如果变量未定义,则使用默认值。
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
获取变量的长度。
name="Ryan"
echo "Your name is ${#name} letters long!"
# => Your name is 4 letters long!
切下符合图案的碎片。
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:
感谢Sanket Patel修复错误!
您可以将匹配的字母大写!
echo ${url^^a}
# => https://AssertnotmAgic.com/About
您可以获得字符串切片。
echo ${url:2:5} # the pattern is ${var:start:len}. Start is zero-based.
# => tps://
您可以替换图案。
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
并且,您可以间接使用变量作为其他变量的名称。
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.
<<双角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.
这个词可以随便用。我一般用“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
$
$ 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!
$
- 如果在初始标记两边加上单引号,HEREDOC 将被视为单引号字符串,变量表达式不会被插入。
$ cat <<EOF
$RANDOM
EOF
4578
$ cat <<'EOF'
$RANDOM
EOF
$RANDOM
- 您还可以使用不带标签的三个小于符号来仅输入一个简单的字符串,如下所示:
$ cat <<EOF
beep
EOF
beep
$ cat <<<beep
beep
$
最后一个技巧是,如果在箭头后添加破折号,它会抑制heredoc 中的任何前导制表符(但不是空格)。
cat <<-HEREDOC
two leading tabs
one leading tab
two spaces here
HEREDOC
# => two leading tabs
# => one leading tab
# => two spaces here
标点符号是杀手
希望以上内容对您有所帮助。如果您发现我遗漏了什么,或者您发现了其他更酷的用法,请务必告诉我,我会更新并公开表扬您的才华。感谢您的阅读!
文章来源:https://dev.to/rpalo/bash-brackets-quick-reference-4eh6
你错过了一个非常棒的 BASH 语法:
<( COMMAND )
。基本上,在子 shell 中运行命令,然后通过文件描述符返回其输出。这意味着你可以执行以下操作:另一个很棒的功能是
VAR=($( COMMAND ))
...它获取输出COMMAND
并从中创建一个数组变量。当您关心命令如何退出而不是其输出时也很有用
$( COMMAND )$?
(例如,您想测试 grep 是否在文件中找到字符串,[[ $( grep -q PATTERN FILE )$? ]]
。