简介
查看当前设备默认shell:
$ echo $SHELL
/bin/bash
但是当前使用的shell并不一定是上述shell。一般来说,ps命令结果的倒数第二行是当前 Shell:
$ ps
PID TTY TIME CMD
4467 pts/0 00:00:00 bash
5379 pts/0 00:00:00 ps
查看当前的 Linux 系统安装的所有 Shell:
$ cat /etc/shells
上面例子中,完整的提示符是[user@hostname] $(用户名@主机名 $)
注意,根用户(root)的提示符,不以美元符号$结尾,而以井号#结尾
可以输入bash命令启动 Bash:
$ bash
退出 Bash 环境,可以使用exit命令,也可以同时按下Ctrl + d:
$ exit
用户可以通过bash命令的--version参数或者环境变量$BASH_VERSION,查看本机的 Bash 版本:
$ bash --version
GNU bash,版本 5.0.3(1)-release (x86_64-pc-linux-gnu)
# 或者
$ echo $BASH_VERSION
5.0.3(1)-release
echo命令
$ echo hello world
hello world
$ echo "hello world"
hello world
多行输出要加""
$ echo "<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
Page body.
</BODY>
</HTML>"
-n参数用于取消回车符(注意第二行$符位置):
$ echo -n hello world
hello world$
;用于分割语句:
$ echo -n a;echo b
ab
-e参数会解释引号(双引号和单引号)里面的特殊字符:
$ echo "Hello\nWorld"
Hello\nWorld
$ echo -e "Hello\nWorld"
Hello
World
命令分行:
$ echo foo \
bar
#等价于
echo foo bar
foo bar
控制命令执行顺序:
#使用分号时,第二个命令总是接着第一个命令执行,不管第一个命令执行成功或失败。
Command1 ; Command2
#使用&&时,如果Command1命令运行成功,则继续运行Command2命令。
Command1 && Command2
#使用||时,如果Command1命令运行失败,则继续运行Command2命令。
Command1 || Command2
type命令用来判断命令的来源:
$ type echo
echo is a shell builtin
$ type ls
ls is hashed (/bin/ls)
可以看到,echo是内部命令,ls是外部程序(/bin/ls)
-a参数用于要查看一个命令的所有定义:
$ type -a echo
echo is shell builtin
echo is /usr/bin/echo
echo is /bin/echo
可以看到,echo命令既是内置命令,也有对应的外部程序。
-t参数可以返回一个命令的类型:别名(alias),关键词(keyword),函数(function),内置命令(builtin)和文件(file):
$ type -t bash
file
$ type -t if
keyword
可以看到,bash是文件,if是关键词。
模式扩展
bash关闭模式扩展:
$ set -o noglob
# 或者
$ set -f
bash打开模式扩展:
$ set +o noglob
# 或者
$ set +f
波浪线~会自动扩展成当前用户的主目录:
$ echo ~
/home/me
# 进入 /home/me/foo 目录
$ cd ~/foo
~user表示扩展成用户user的主目录。
$ echo ~foo
/home/foo
$ echo ~root
/root
如果~user的user是不存在的用户名,则波浪号扩展不起作用:
$ echo ~nonExistedUser
~nonExistedUser
~+会扩展成当前所在的目录,等同于pwd命令:
$ cd ~/foo
$ echo ~+
/home/me/foo
?字符代表文件路径里面的任意单个字符,不包括空字符:
# 存在文件 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt
# 存在文件 a.txt、b.txt 和 ab.txt
$ ls ??.txt
ab.txt
# 当前目录为空目录
$ echo ?.txt
?.txt
*字符代表文件路径里面的任意数量的任意字符,包括零个字符:
# 存在文件 a.txt、b.txt 和 ab.txt
$ ls *.txt
a.txt b.txt ab.txt
注意,*不会匹配隐藏文件(以.开头的文件),即ls *不会输出隐藏文件。
如果要匹配隐藏文件,同时要排除.和..这两个特殊的隐藏文件,可以与方括号扩展结合使用:
$ echo .[!.]*
匹配子目录文件:
# 子目录有一个 a.txt
# 无效的写法
$ ls *.txt
# 有效的写法
$ ls */*.txt
Bash 4.0 引入了一个参数globstar,当该参数打开时,允许**匹配零个或多个子目录。因此,**/*.txt可以匹配顶层的文本文件和任意深度子目录的文本文件。
方括号[]扩展匹配括号中任意字符:
# 存在文件 a.txt 和 b.txt
$ ls [ab].txt
a.txt b.txt
# 不存在文件 a.txt 和 b.txt
$ ls [ab].txt
ls: 无法访问'[ab].txt': 没有那个文件或目录
[^...]和[!...]表示匹配不在方括号里面的字符,这两种写法是等价的:
# 存在 aaa、bbb、aba 三个文件
$ ls ?[!a]?
aba bbb
注意,如果需要匹配[字符,可以放在方括号内,比如[[aeiou]。如果需要匹配连字号-,只能放在方括号内部的开头或结尾,比如[-aeiou]或[aeiou-]。
方括号扩展有一个简写形式[start-end],表示匹配一个连续的范围。比如,[a-c]等同于[abc],[0-9]匹配[0123456789]。
# 存在文件 a.txt、b.txt 和 c.txt
$ ls [a-c].txt
a.txt
b.txt
c.txt
[!start-end]表示匹配不属于这个范围的字符。比如,[!a-zA-Z]表示匹配非英文字母的字符。
大括号扩展{...}表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔:
$ echo {1,2,3}
1 2 3
$ echo d{a,e,i,u,o}g
dag deg dig dug dog
$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back
注意,大括号扩展不是文件名扩展。它会扩展成所有给定的值,而不管是否有对应的文件存在:
$ ls {a,b,c}.txt
ls: 无法访问'a.txt': 没有那个文件或目录
ls: 无法访问'b.txt': 没有那个文件或目录
ls: 无法访问'c.txt': 没有那个文件或目录
另一个需要注意的地方是,大括号内部的逗号前后不能有空格。否则,大括号扩展会失效:
#Bash认为这不是大括号扩展,而是三个独立的参数。
$ echo {1 , 2}
{1 , 2}
逗号前面可以没有值,表示扩展的第一项为空:
$ cp a.log{,.bak}
# 等同于
# cp a.log a.log.bak
大括号嵌套:
$ echo {j{p,pe}g,png}
jpg jpeg png
$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
大括号也可以与其他模式联用,并且总是先于其他模式进行扩展:
$ echo /bin/{cat,b*}
/bin/cat /bin/b2sum /bin/base32 /bin/base64 ... ...
# 基本等同于
$ echo /bin/cat;echo /bin/b*
由于大括号扩展{...}不是文件名扩展,所以它总是会扩展的。这与方括号扩展[...]完全不同,如果匹配的文件不存在,方括号就不会扩展。这一点要注意区分:
# 不存在 a.txt 和 b.txt,[]不会进行扩展
$ echo [ab].txt
[ab].txt
#但是{}会进行扩展
$ echo {a,b}.txt
a.txt b.txt
大括号扩展有一个简写形式{start..end},表示扩展成一个连续序列。比如:
$ echo {a..c}
a b c
$ echo d{a..d}g
dag dbg dcg ddg
$ echo {5..1} #支持逆序
5 4 3 2 1
$ echo {a1..3c} #解释器无法理解则原样输出
{a1..3c}
大括号扩展的常见用途为新建一系列目录:
#新建36个子目录,每个子目录的名字都是”年份-月份“
$ mkdir {2007..2009}-{01..12}
这个写法的另一个常见用途,是直接用于for循环:
for i in {1..5}
do
echo $i
done
如果整数前面有前导0,扩展输出的每一项都有前导0:
$ echo {01..5}
01 02 03 04 05
$ echo {001..5}
001 002 003 004 005
这种简写形式还可以使用第二个双点号(start..end..step),用来指定扩展的步长:
$ echo {0..8..2}
0 2 4 6 8
多个简写形式连用,会有循环处理的效果:
$ echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3
bash变量:
$ echo $SHELL
/bin/bash
$ echo ${SHELL}
/bin/bash
${!string*}或${!string@}返回所有匹配给定字符串string的变量名:
$ echo ${!S*}
SECONDS SHELL SHELLOPTS SHLVL SSH_AGENT_PID SSH_AUTH_SOCK
上面例子中,${!S*}扩展成所有以S开头的变量名。
$(...)可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。同样,子命令放在反引号中也可以:
$ echo $(date)
Tue Jan 28 00:01:13 CST 2020
$ echo `date`
Tue Jan 28 00:01:13 CST 2020
$(...)可以嵌套,比如$(ls $(pwd))。
$((...))可以扩展成整数运算的结果
$ echo $((2 + 2))
4
[[:class:]]表示一个字符类,扩展成某一类特定字符之中的一个:
#输出所有大写字母开头的文件名
$ echo [[:upper:]]*
#输出所有所有非数字开头的文件名
$ echo [![:digit:]]*
Bash 允许文件名使用通配符,即文件名包括特殊字符。这时引用文件名,需要把文件名放在单引号或双引号里面:
$ touch 'fo*'
$ ls
fo*
量词语法用来控制模式匹配的次数。它只有在 Bash 的extglob参数打开的情况下才能使用,不过一般是默认打开的。下面的命令可以查询:
$ shopt extglob
extglob on
如果extglob参数是关闭的,可以用下面的命令打开:
$ shopt -s extglob
两次语法如下:
?(pattern-list):模式匹配零次或一次。
*(pattern-list):模式匹配零次或多次。
+(pattern-list):模式匹配一次或多次。
@(pattern-list):只匹配一次模式。
!(pattern-list):匹配给定模式以外的任何内容。
举例:
#?(.)匹配零个或一个点
$ ls abc?(.)txt
abctxt abc.txt
#?(def)匹配零个或一个def
$ ls abc?(def)
abc abcdef
#@(.txt|.php)匹配文件有且只有一个.txt或.php后缀名
$ ls abc@(.txt|.php)
abc.php abc.txt
#+(.txt)匹配文件有一个或多个.txt后缀名
$ ls abc+(.txt)
abc.txt abc.txt.txt
#!(b)表示匹配单个字母b以外的任意内容,所以除了ab.txt以外,其他文件名都能匹配
$ ls a!(b).txt
a.txt abb.txt ac.txt
shopt命令可以调整 Bash 的行为。它有好几个参数跟通配符扩展有关:
# 打开某个参数
$ shopt -s [optionname]
# 关闭某个参数
$ shopt -u [optionname]
# 查询某个参数关闭还是打开
$ shopt [optionname]
dotglob参数可以让扩展结果包括隐藏文件(即点开头的文件):
$ shopt -s dotglob
$ ls *
abc.txt .config
nullglob参数可以让通配符不匹配任何文件名时,返回空字符:
#默认情况下:
$ rm b*
rm: 无法删除'b*': 没有那个文件或目录
#nullglob打开时,由于没有b*匹配的文件名,所以rm b*扩展成了rm:
$ shopt -s nullglob
$ rm b*
rm: 缺少操作数
failglob参数使得通配符不匹配任何文件名时,Bash 会直接报错,而不是让各个命令去处理:
#由于b*不匹配任何文件名,Bash 直接报错了,不再让rm命令去处理
$ shopt -s failglob
$ rm b*
bash: 无匹配: b*
nocaseglob参数可以让通配符扩展不区分大小写:
$ shopt -s nocaseglob
$ ls /windows/program*
/windows/ProgramData
/windows/Program Files
/windows/Program Files (x86)
globstar参数可以使得**匹配零个或多个子目录:
$ shopt -s globstar
$ ls **/*.txt
a.txt sub1/b.txt sub1/sub2/c.txt
#如果globstar没有打开,只能如下匹配:
$ ls *.txt */*.txt */*/*.txt
a.txt sub1/b.txt sub1/sub2/c.txt
转义
\用于转义:
$ echo $date
$ echo \$date
$date
$ echo \\
\
$ echo \ #命令换行符
>
一些不可打印的字符(这些必须在引号内):
\a:响铃
\b:退格
\n:换行
\r:回车
\t:制表符
如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数:
$ echo a\tb
atb
$ echo -e "a\tb"
a b
换行符(回车)是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,Bash 会将其当作长度为0的空字符处理,从而可以将一行命令写成多行:
$ mv \
/path/to/foo \
/path/to/bar
# 等同于
$ mv /path/to/foo /path/to/bar
Bash 允许字符串放在单引号或双引号之中,加以引用,变为字符串:
$ echo '$USER'
$USER
$ echo '$((2+2))'
$((2+2))
由于反斜杠在单引号里面变成了普通字符,所以如果单引号之中,还要使用单引号,不能使用转义,需要在外层的单引号前面加上一个美元符号$,然后再对里层的单引号转义:
# 不正确
$ echo 'it\'s'
# 正确
$ echo $'it\'s'
不过,更合理的方法是改在双引号之中使用单引号:
$ echo "it's"
it's
注意,引号中的通配符不会发挥作用:
$ echo '*'
*
不过双引号更宽松。美元符号$、反引号`和反斜杠\。这三个字符在双引号之中,依然有特殊含义,会被 Bash 自动扩展:
$ echo "$SHELL" #$表示变量
/bin/bash
$ echo "`date`" #``表示执行命令
Mon Jan 27 13:33:18 CST 2020
$ echo "I'd say: \"hello!\"" #\表示转义
I'd say: "hello!"
$ echo "\\"
\
对比:
$ echo "\\"
\
$ echo '\\'
\\
换行符在双引号之中,会失去特殊含义,Bash 不再将其解释为命令的结束,只是作为普通的换行符。所以可以利用双引号,在命令行输入多行文本:
$ echo "hello
world"
hello
world
双引号还有一个作用,就是保存原始命令的输出格式:
$ cal
一月 2020
日 一 二 三 四 五 六
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
# 单行输出
$ echo $(cal)
一月 2020 日 一 二 三 四 五 六 1 2 3 ... 31
# 原始格式输出
$ echo "$(cal)"
一月 2020
日 一 二 三 四 五 六
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
HERE文档
Here 文档(here document)是一种输入多行字符串的方法,格式如下:
<< token
text
token
它的格式分成开始标记(<< token)和结束标记(token)。开始标记是两个小于号 + Here 文档的名称,名称可以随意取,后面必须是一个换行符;结束标记是单独一行顶格写的 Here 文档名称,如果不是顶格,结束标记不起作用。两者之间就是多行字符串的内容。
$ foo='hello world'
$ cat << _example_
$foo
"$foo"
'$foo'
"\\"
_example_
hello world
"hello world"
'hello world'
"\"
可以发现,Here 文档内部会发生变量替换,同时支持反斜杠转义(在Here文档中单引号和双引号内部均可以转义,与非Here文档环境不同),但是不支持通配符扩展,双引号和单引号也失去语法作用,变成了普通字符。
如果不希望发生变量替换,可以把 Here 文档的开始标记放在单引号之中:
$ foo='hello world'
$ cat << '_example_'
$foo
"$foo"
'$foo'
_example_
$foo
"$foo"
'$foo'
Here 文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了echo命令:
$ command << token
string
token
# 等同于
$ echo string | command
所以,Here 字符串只适合那些可以接受标准输入作为参数的命令,对于其他命令无效,比如echo命令就不能用 Here 文档作为参数。
HERE字符串
它的作用是将字符串通过标准输入,传递给命令:
有些命令直接接受给定的参数,与通过标准输入接受参数,结果是不一样的,所以才有了这个语法,
$ cat <<< 'hi there'
# 等同于
$ echo 'hi there' | cat
md5sum命令只能接受标准输入作为参数,不能直接将字符串放在命令后面,会被当作文件名,即md5sum ddd里面的ddd会被解释成文件名。这时就可以用 Here 字符串,将字符串传给md5sum命令:
$ md5sum <<< 'ddd'
# 等同于
$ echo 'ddd' | md5sum