之前写过两篇关于Bash语法的blog,分别是:
Bash特殊变量(Bash Special Variables)
个人感觉,想要通畅地读懂bash脚本,还差一个部分,那就是符号。
个人网上的讲bash符号的文章有点乱,要么有错,要么不全,要么太深,看了也感觉用不上。很多英文的文章写的好,写的深,但是查阅起来不方便。于是,干脆我自己写一篇吧,不说我这篇写的有多好,起码名称中英文有个对应,有一些自己动手运行验证过了的小例子,很多技术细节我也注明了出处。将来如果有对细节进一步的查阅的需要,可以从这里开始。
# 井号 (crosshatch/sharp/hash)
1. 井号开头的语句被视为代码的注释,不会被执行。
2. 在 #!/bin/bash 中,脚本文件开头首行的#!会告诉系统这个文件是一系列命令的集合,应该被发送给#!后面指定的解释器来执行。大概都有哪些解释器呢?如下:
- #!/bin/sh :Executes the script using the Bourne shell or a compatible shell, with path /bin/sh
- #!/bin/bash :Executes the script using the Bash shell.
- #!/bin/csh -f :Executes the script using C shell or a compatible shell.
- #!/usr/bin/perl -T :Executes the script using perl with the option of taint checks
- #!/usr/bin/env python :Executes the script using python by looking up the path to the python interpreter automatically from the environment variables
~ 波浪号(tilde)
Bash提供了一些用~开头的变量,这些变量的展开叫做Tilde Expansions,也就是将这些缩写转换成它们代表的目录名称的过程。
1. 与$HOME一样,代表当前用户的home directory.
2. ~username, 代表其他某用户的home directory.
3. ~+, 代表当前的工作目录, 与$PWD一样。
4. ~-,代表上一个工作目录,与$OLDPWD一样。
5. ~1, ~2, ~-1, ~-0 分别代表目录栈中的编号为1的,编号为2的,编号为倒数第2的,倒数第一的目录。相当于‘dirs +1’, ‘dirs +2’, ‘dirs -1’, ‘dirs –0’.
下面展示了一个例子。关于操作目录栈的具体内容,请看这里。
; 分号(semicolon)
分号用来分隔两个命令,比如命令 echo a; echo b 就是告诉bash,echo a 和 echo b 是两个不同的命令,需要一个接一个地分开运行。参考这里。
;; 双分号(double semicolon)
双分号;; 仅用在case结构中,标志一个选项的结束,类似于C语言中的break。
1 2 3 4 | case $answer in yes) echo 'yay!';; no) echo 'boo!';; esac |
. 点号(dot)
一个 dot 代表当前目录,两个 dot 代表上层目录。如果档案名称以 dot 开头,该档案就属特殊档案,用 ls 指令必须加上 -a 选项才会显示。除此之外,在 regular expression 中,一个 dot 代表匹配一个字元。
‘string' 单引号 (single quote)
被单引号用括住的内容,将被视为单一字串。在引号内的代表变量的$符号,没有作用,也就是说,$符号被视为一般符号处理,防止任何变量替换。
“string” 双引号 (double quote)
被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引数的处理方式不同。
`command` 反引号 (backtick)
反引号不是一种引号,它有特殊的意义。任何被反引号括起来的东西都会在主命令执行之前先被shell求值(或执行),并且反引号括起来的东西的求值(或执行)的输出会被主命令使用到。
在前面的单双引号,括住的是字串,但如果该字串是一列命令列,会怎样?命令会执行么?
答案是不会执行。要处理这种情况,我们得用反引号。
在反引号内的 date +%F 会被视为指令,执行的结果会带入 $fdv 变量中。
下面的例子里,先创建一个名为lucky的user,可以看到他的uid为1001.
查看当前用户的信息,名为yunlong的用户的uid为1000. 可以看到/home/lucky 是lucky用户的home directory,owner是lucky。
然后,我们利用命令 chown `id -u` /home/lucky, 把/home/lucky的owner换成了yunlong。注意这里对于反引号的使用。
关于反引号的更多信息,看这里。
, 逗号 (comma)
逗号可以有下面这些用途:
1. 括号扩展(brace expansion)
tee 命令的作用是从标准输入读取(standard input)之后,将读取来的信息同时写入标准输出(standard output)和一个或多个文件中。详情见这里。
2. 在let语句中,或者在与其作用等价的(())构造中分隔算数操作。
3. 可以在for语句中,分隔不同的index变量,避免在for循环体中进行操作。
4. 逗号还可以用在Bash4中,把字符串变成小写。
关于逗号在bash中的用途,原文看这里。
/ 斜杠 (forward slash)
在路径表示时,斜杠代表目录。通常单一的斜杠 / 代表 root 根目录的意思;在四则运算中,斜杠代表除法的符号。
反斜杠(backslash/escape)
在交互模式下的escape符号,有几个作用:
1. 放在指令前,有取消 aliases 的作用;
# type rm
rm is aliased to `rm -i'
# m .*.log
上例,作者在 rm 指令前加上 escape 字符,作用是暂时取消别名的功能,将 rm 指令还原。
2. 转义字符,放在特殊符号前,则该特殊符号的作用消失;
3. 放在指令的最末端,表示指令连接下一行。
| 竖线 bar/pipe/vertical bar
pipeline 是 UNIX 系统,基础且重要的观念。连结上个指令的标准输出,做为下个指令的标准输入。
善用这个观念,对精简 script 有相当的帮助。
! 叹号 exclamation mark/bang
通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表”不等于”
# if [ "$?" != 0 ] ; then echo “Executes error”; elif then echo "Executes successfully"; fi;
这个例子里,除了展示了叹号的用法,还包含了如何把if语句写在一行里的方法,还有一个利用上面讲过的反斜杠把一个一行的if语句写成多行的方法。
在正则表达式里,可以使用!
字符在正则表达式之前对其求反。也就是说,仅当字符串与表达式的其余部分不匹配时,才认为字符串已匹配。
注意,网上的很多名为《shell脚本中一些特殊符号》的文章里,在讲叹号是给出的例子里使用叹号是不正确的。
应该使用^符号,来对字符范围取反。如下:
: 冒号 colon
在 bash 中,这是一个内建指令, 这个指令”什么事都不干(no-op)”,但返回状态值 0 (Bash里返回值为0表示正常执行结束,非0表示出了问题)。
比如,if语句利用上冒号可以写成这样:
# if [ "$?" != 0 ]; then :; else echo "Execute successfully"; fi
关于冒号在Bash中的应用,具体可以看这里。
? 问号 (question mark)
在文件名的匹配中,用作单字符的通配符。注意,问号代表的是只有一个字符的通配符,数字和字母都可以匹配。
* 星号 (asterisk/star)
星号是bash中的任意字符序列通配符,包括没有字符的情况都可以匹配。
** 双星号 (double-asterisk)
双星号(**)在多个段中尝试匹配零个或多个字符,它用于对嵌套目录中的文件进行通配。
举例:
Tests/**/*.js
这里,匹配的文件被限制在Test目录中。会被匹配的文件包括Tests/HelloWorld.js
, Tests/UI/HelloWorld.js
, Tests/UI/Feature1/HelloWorld.js
更多信息,看这里。
另一个就是幂运算。
$ 美元符号(dollar sign)
变量替换(Variable Substitution)的代表符号。 一个变量名字前面带上美元符号,表明这是个变量。可以用美元符带上这个变量的名字对其进行引用。
另外,在 Regular Expressions 里被定义为匹配行尾 (end-of-line)。
还有很多其他特殊变量是用美元符号开头的,参考这里。
${} 变量扩展(variable substitution)
${ }用于变量替换。一般情况下,$var 与${var} 并没有啥不一样。但是用 ${ } 会比较精确的界定变量名称的范围。
$ AB=string0
$ A=string1
$ echo $ABstring0
# 原本是打算先将 $A 的结果替换出来,然后再补一个 B 字母于其后.# 但在命令行上,真正的结果却是只会提换变量名称为 AB 的值出来…
# 若使用 ${ } 就没问题了:
$ echo ${A}Bstring1B
$() 命令替换 (command substitution,与一对反引号相同)
在 bash shell 中,$( ) 与` ` (反引号) 都是用来做命令替换用(command substitution)的。
例如version=$(uname -r)和version=`uname -r`都可以是version得到内核的版本号。
1. 反引号的命令替换基本上可在全部的 unix shell 中使用,若写成 shell script ,其移植性比较高。但是,反引号容易打错或看错。
2. $()并不是所有shell都支持。
$(()) 和 $[] 算数扩展(arithmetic expansion)
它们是一样的,都是进行数学运算的。支持+ - * / %:分别为 “加、减、乘、除、取模”。但是注意,bash只能作整数运算,对于浮点数是当作字符串处理的。
二者的区别是$[]已经是淘汰的,弃用的语法了,现在已经完全被$(())替代了。
() 小括号(parentheses) 指令群组 (command group)
小括号之内的一系列命令,会在一个新创建的subshell中执行。由于小括号内的命令是在subshell中执行的,所以其内部的变量赋值在执行之后不会持续。
Placing a list of commands between parentheses causes a subshell environment to be created (see Command Execution Environment), and each of the commands in list to be executed in that subshell. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
{} 花括号(braces)指令群组 (command group)
花括号之内的一系列指令会在当前的shell的上下文中执行。不会创建subshell。 列表之后的分号(或者换行)是必须要有的。
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The semicolon (or newline) following list is required.
具体解释参考这里。
(()) 双括号 (Double-Parentheses)
跟let命令一样,双括号允许在其中进行算数扩展和求值。 最简单的一个例子, a=$((5+3)), 会将5+3的结果赋值给a。 双括号还是一个在bash中使用的C语言风格的操作变量的方法,举个例子 ((var++)).
详情看这里。
[] 单方括号 (Single Square Brackets)
单方括号是内置命令test的另一种形式,方括号括住的命令会被验证其是否为真。 注意,空字符串为false,非空字符串为true。
既然是test命令的另一种形式,那么test命令的各种选项就也是可以应用的。
具体看这里。
[[]] 双方括号 (Double Square Brackets)
[[ 是新的,提高版的[, 双方括号是个关键字,不是一个程序。双方框更好用,比较见下表。
英文原文如下:
[ : test implements the old, portable syntax of the command. In almost all shells (the oldest Bourne shells are the exception), [ is a synonym for test (but requires a final argument of ]).
[[ : is a new, improved version of it, and it is a keyword rather than a program. This makes it easier to use
&& 逻辑与, || 逻辑或 (Logical Operators)
双&&符号在Bash中意思是逻辑与(AND),并且可以用来分隔几个顺序执行的命令。
举例:
#
cd /root/ && echo "I've got root"
具体看这里。
& 后台工作 (Single Ampersand)
在Bash中, &是控制字符,放在命令的末尾,其作用是让命令在后台运行,也就是说在另外的一个subshell里,以异步的方式,如同一个job一样的执行。当前的Shell会立即回到等待用户输入的状态,并且返回值为0.
举例:
# shell会给出后台运行命令的process ID (PID), 这个ID会存储在$! 变量中。
$ ./myscript.py &
[1] 1337
# 后面可以通过$!变量对该进程进行引用。
$ echo $!
1337
# 一旦生成了新的后台进程,这个进程会出现在job列表中。
$ jobs
[1]+ Running ./myscript.py &
# foregroud命令可以把这个进程拿回前台。
fg
具体看这里。
+ 加号, -减号, * 乘号, / 除号
比较简单,不说了。
= 等号(赋值), == 双等号 (判断相等), != 不等于号
简单,也不说了。
^ 折音号 (circumflex/caret)
这个符号在正则表达式中,代表行的开头位置,在[]中也与!(叹号)一样表示非,即求反。
>, >>, <, <<, >&, <&, <<tag, 2>, 2>>, 2>&1 输入输出重定向
这里符号比较多,一个一个简介吧。
1. command > file --- 将输出重定向到file
2. command < file --- 将输入重定向到file,确切的说是把file中的内容拿来作为command的输入。
3. command >> file --- 将输出以追加的方式重定向到file
4. n >& m --- 将输出文件m和n合并
5. n <& m --- 将输入文件m和n合并
6. << tag --- 将开始标记 tag 和结束标记 tag 之间的内容作为输入。
输入重定向举例:
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
- 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
- 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
- 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
如果希望 stderr 重定向到 file,可以这样写:
$ command 2>file
如果希望 stderr 追加到 file 文件末尾,可以这样写:
$ command 2>>file
2 表示标准错误文件(stderr)。如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
$ command > file 2>&1
$ # 或者
$ command >> file 2>&1
更多信息,看这里。
参考资料
=============
Shell中的特殊符号和含义简明总结
https://blog.csdn.net/wejfoasdbsdg/article/details/53289589
shell脚本中一些特殊符号
https://www.cnblogs.com/xuxm2007/archive/2011/10/20/2218846.html
(#!/bin/bash ) What exactly is this ?
https://medium.com/@codingmaths/bin-bash-what-exactly-is-this-95fc8db817bf
目录堆栈
https://wangdoc.com/bash/stack.html
How to Create Users in Linux (useradd Command)
https://linuxize.com/post/how-to-create-users-in-linux-using-the-useradd-command/
The Magic ~: Bash Tilde Expansion with 5 Examples