shell相关1

简介

查看当前设备默认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

参考:https://wangdoc.com/bash/intro.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容