Bash小结

Bash的基本语法

1.echo命令

echo命令的作用是在屏幕输出一行文本

1
2
$ echo hello
hello

果想要输出的是多行文本,即包括换行符,需要把多行文本放在引号里

1
2
3
4
5
6
7
8
$ echo "<HTML>
    <HEAD>
          <TITLE>Page Title</TITLE>
    </HEAD>
    <BODY>
          Page body.
    </BODY>
</HTML>"

-n 参数

默认情况,echo输出的文本末尾会有一个回车符.-n参数可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面.

1
2
3
4
5
$ echo a;echo b
a
b
$ echo -n a;echo b
ab

-e参数

-e参数会解释引号内的特殊字符

如不使用-e参数,即默认情况下,引号会让特殊字符变成普通字符.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ echo "Hello\nWorld"
Hello\nWorld

# 双引号的情况
$ echo -e "Hello\nWorld"
Hello
World

# 单引号的情况
$ echo -e 'Hello\nWorld'
Hello
World

2.命令格式

命令行环境中,主要使用Shell命令,格式为

1
2
$ command [arg1 ... [argN]]
# ls -l

command是具体命令或可执行文件,arg为传递给命令的参数 是可选的.

1
2
3
4
5
# 短形式
$ ls -r

# 长形式
$ ls --reverse

Bash 单个命令一般都是一行,用户按下回车键,就开始执行。有些命令比较长,写成多行会有利于阅读和编辑,这时可以在每一行的结尾加上反斜杠,Bash 就会将下一行跟当前行放在一起解释。

1
2
3
4
5
$ echo foo bar

# 等同于
$ echo foo \
bar

3.空格

Bash使用空格(或Tab)区分不同的参数

1
$ command foo bar

如果参数之间有多个空格,Bash会自动忽略多余的空格

1
2
# echo this is a      test
this is a test

4.分号

分号是命令结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令.

1
# clear; ls

5.命令的组合符&&||

1
Command1 && Command2

如果Command1运行成功,则继续运行Command2命令

1
Command1 || Command2

Command1运行失败,则继续运行Command2

6.type命令

用来查询一个命令式内部命令还是外部命令

1
2
3
4
$ type echo
echo is a shell builtin
$ type ls
ls is hashed (/bin/ls)

type命令的-t参数,可以返回一个命令的类型:别名(alias),关键词(keyword),函数(function),内置命令(builtin)和文件(file)。

7.快捷键

常用的快捷键

  • Ctrl + L:清除屏幕井将当前行移到页面顶部
  • Ctrl + C:中止当前正在执行的命令
  • Shift + PageUp:向上滚动
  • Shift + PageDown:向下滚动
  • Ctrl + U:从光标位置删除到首行
  • Ctrl + K:从光标位置删除到尾行
  • Ctrl + D:关闭Shell会话
  • :浏览已执行命令的历史记录

Bash的模式扩展

Shell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。

这种特殊字符的扩展,称为模式扩展(globbing)。其中有些用到通配符,又称为通配符扩展(wildcard expansion)。Bash 一共提供八种扩展。

  • 波浪线扩展
  • ? 字符扩展
  • * 字符扩展
  • 方括号扩展
  • 大括号扩展
  • 变量扩展
  • 子命令扩展
  • 算术扩展

2.波浪线扩展

波浪线 ~ 会自动扩展成当前用户的主目录

1
2
$ echo ~
/home/User

~/dir表示扩展成主目录的某个子目录,dir是主目录里面的一个子目录名

1
$ cd ~/foo

3.?字符扩展

?字符代表文件路径里面的任意单个字符,不包括空字符。比如,Data???匹配所有Data后面跟着三个字符的文件名。

1
2
3
# 存在文件 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt

?表示单个字符,多个字符需要多个?连用

1
2
3
# 存在文件 a.txt、b.txt 和 ab.txt
$ ls ??.txt
ab.txt

4.*字符扩展

*字符代表文件路径里面的任意数量的字符,包括零个字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 存在文件 a.txt、b.txt 和 ab.txt
$ ls *.txt
a.txt b.txt ab.txt

# 输出所有文件
$ ls *

# 存在文件 a.txt、b.txt 和 ab.txt
$ ls a*.txt
a.txt ab.txt

$ ls *b*
b.txt ab.txt

# 注意,*不会匹配隐藏文件(以.开头的文件)。

# 显示所有隐藏文件
$ echo .*

# 与方括号扩展结合使用,
# 只显示正常的隐藏文件,不显示 . 和 .. 这两个特殊文件
$ echo .[!.]*

5.方括号扩展

方括号扩展的形式是[...],只有文件确实存在的前提下才会扩展。如果文件不存在,就会原样输出。括号之中的任意一个字符。比如,[aeiou]可以匹配五个元音字母中的任意一个。

1
2
3
4
5
6
7
# 存在文件 a.txt 和 b.txt
$ ls [ab].txt
a.txt b.txt

# 只存在文件 a.txt
$ ls [ab].txt
a.txt

方括号扩展还有两种变体:[^...][!...]。它们表示匹配不在方括号里面的字符,这两种写法是等价的。比如,[^abc][!abc]表示匹配除了abc以外的字符。

1
2
3
# 存在 aaa、bbb、aba 三个文件
$ ls ?[!a]?
aba bbb

6.[start-end]扩展

方括号扩展一个简写形式[start-end],表示匹配一个连续的范围。比如,[a-c]等同于[abc][0-9]匹配[0123456789]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 存在文件 a.txt、b.txt 和 c.txt
$ ls [a-c].txt
a.txt
b.txt
c.txt

# 存在文件 report1.txt、report2.txt 和 report3.txt
$ ls report[0-9].txt
report1.txt
report2.txt
report3.txt
...

下面是一些常见简写的例子

  • [a-z]:所有小写字母。
  • [a-zA-Z]:所有小写字母与大写字母。
  • [a-zA-Z0-9]:所有小写字母、大写字母与数字。
  • [abc]*:所有以abc字符之一开头的文件名。
  • program.[co]:文件program.c与文件program.o
  • BACKUP.[0-9][0-9][0-9]:所有以BACKUP.开头,后面是三个数字的文件名。

这种简写形式有一个否定形式[!start-end],表示匹配不属于这个范围的字符。比如,[!a-zA-Z]表示匹配非英文字母的字符。

1
2
$ echo report[!1–3].txt
report4.txt report5.txt

7.大括号扩展

大括号扩展{...}表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔。比如,{1,2,3}扩展成1 2 3

1
2
3
4
5
6
7
8
$ 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

注意,大括号扩展不是文件名扩展。它会扩展成所有给定的值,而不管是否有对应的文件存在。

1
2
3
4
$ ls {a,b,c}.txt
ls: 无法访问'a.txt': 没有那个文件或目录
ls: 无法访问'b.txt': 没有那个文件或目录
ls: 无法访问'c.txt': 没有那个文件或目录

上面例子中,即使不存在对应的文件,{a,b,c}依然扩展成三个文件名,导致ls命令报了三个错误。

另一个需要注意的地方是,大括号内部的逗号前后不能有空格。否则,大括号扩展会失效。

1
2
$ echo {1 , 2}
{1 , 2}

上面例子中,逗号前后有空格,Bash 就会认为这不是大括号扩展,而是三个独立的参数。

逗号前面可以没有值,表示扩展的第一项为空。

1
2
3
4
$ cp a.log{,.bak}

# 等同于
# cp a.log a.log.bak

大括号可以嵌套。

大括号也可以与其他模式联用,并且总是先于其他模式进行扩展。

上面例子中,会先进行大括号扩展,然后进行*扩展。

大括号可以用于多字符的模式,方括号不行(只能匹配单字符)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ echo {j{p,pe}g,png}
jpg jpeg png

$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b

$ echo {cat,d*}
cat dawg dg dig dog doug dug

$ echo {cat,dog}
cat dog

由于大括号扩展{...}不是文件名扩展,所以它总是会扩展的。这与方括号扩展[...]完全不同,如果匹配的文件不存在,方括号就不会扩展。这一点要注意区分。

1
2
3
4
5
6
# 不存在 a.txt 和 b.txt
$ echo [ab].txt
[ab].txt

$ echo {a,b}.txt
a.txt b.txt

8.{start..end}扩展

大括号扩展有一个简写形式{start..end},表示扩展成一个连续序列。比如,{a..z}可以扩展成26个小写英文字母。

 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
32
33
34
35
36
37
$ echo {a..c}
a b c

$ echo d{a..d}g
dag dbg dcg ddg

$ echo {1..4}
1 2 3 4

$ echo Number_{1..5}
Number_1 Number_2 Number_3 Number_4 Number_5

#支持逆序
$ echo {c..a}
c b a

$ echo {5..1}
5 4 3 2 1

# 注意,如果遇到无法理解的简写,大括号模式就会原样输出,不会扩展。
$ echo {a1..3c}
{a1..3c}

# 这种简写形式可以嵌套使用,形成复杂的扩展
$ echo .{mp{3..4},m4{a,b,p,v}}
.mp3 .mp4 .m4a .m4b .m4p .m4v

# 大括号扩展的常见用途为新建一系列目录。
$ mkdir {2007..2009}-{01..12}

上面命令会新建36个子目录,每个子目录的名字都是”年份-月份“。
这个写法的另一个常见用途,是直接用于for循环。

for i in {1..4}
do
  echo $i
done

如果整数前面有前导0,扩展输出的每一项都有前导0:

1
2
3
4
5
$ echo {01..5}
01 02 03 04 05

$ echo {001..5}
001 002 003 004 005

这种简写形式还可以使用第二个双点号(startendstep),用来指定扩展的步长。

1
2
$ echo {082}
0 2 4 6 8

上面代码将0扩展到8,每次递增的长度为2,所以一共输出5个数字。

多个简写形式连用,会有循环处理的效果

1
2
$ echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3

9 变量扩展

1
2
3
4
5
6
7
8
9
$ 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开头的变量名。

10 子命令扩展

$(...)可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。

1
2
$ echo $(date)
Tue Jan 28 00:01:13 CST 2020

11.算数扩展

$((...))可以扩展成整数运算的结果,详见《Bash 的算术运算》一章。

1
2
$ echo $((2 + 2))
4

12.字符串

[[:class:]]表示一个字符类,扩展成某一类特定字符之中的一个。常用的字符类如下。

  • [[:alnum:]]:匹配任意英文字母与数字
  • [[:alpha:]]:匹配任意英文字母
  • [[:blank:]]:空格和 Tab 键。
  • [[:cntrl:]]:ASCII 码 0-31 的不可打印字符。
  • [[:digit:]]:匹配任意数字 0-9。
  • [[:graph:]]:A-Z、a-z、0-9 和标点符号。
  • [[:lower:]]:匹配任意小写字母 a-z。
  • [[:print:]]:ASCII 码 32-127 的可打印字符。
  • [[:punct:]]:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
  • [[:space:]]:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
  • [[:upper:]]:匹配任意大写字母 A-Z。
  • [[:xdigit:]]:16进制字符(A-F、a-f、0-9)。

13.使用注意点

  1. 通配符是先解释,再执行
  2. 文件名扩展在不匹配时,会原样输出
  3. 只适用于单层路径

引号和转义

1.转义

某些字符在Bash里面有特殊含义($ & *)

输出$date不会有任何结果,因为$是一个特殊字符。

如果想要原样输出这些特殊字符,就必须在它们前面加上反斜杠,使其变成普通字符。这就叫做“转义”(escape)。

1
2
3
4
$ echo \$date
$date
$ echo \\
\

反斜杠本身也是特殊字符,如果想要原样输出反斜杠,就需要对它自身转义.

  • \a:响铃
  • \b:退格
  • \n:换行
  • \r:回车
  • \t:制表符

如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数。

1
2
3
4
5
$ echo a\tb
atb

$ echo -e "a\tb"
a        b

由于反斜杠可以对换行符转义,使得 Bash 认为换行符是一个普通字符,从而可以将一行命令写成多行。

1
2
3
4
5
6
$ mv \
/path/to/foo \
/path/to/bar

# 等同于
$ mv /path/to/foo /path/to/bar

如果一条命令过长,就可以在行尾使用反斜杠,将其改写成多行。这是常见的多行命令的写法。

2.单引号

Bash允许字符串放在单引号或双引号之中,加以引用.

单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号(*)、美元符号($)、反斜杠(\)等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ echo '*'
*

$ echo '$USER'
$USER

$ echo '$((2+2))'
$((2+2))

$ echo '$(echo foo)'
$(echo foo)

3.双引号

双引号比单引号宽松,可以保留大部分特殊字符的本来含义,但是三个字符除外:美元符号($)、反引号(``)和反斜杠(`)。也就是说,这三个字符在双引号之中,会被 Bash 自动扩展。

双引号会保存原始命令的输出格式.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 单行输出
$ 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

如果$(cal)不放在双引号之中,echo就会将所有结果以单行输出,丢弃了所有原始的格式。

4.Here文档

Here文档是一种输入多行字符串的方法,格式如下

1
2
3
<< token
text
token

它的格式分成开始标记(<< token)和结束标记(token)。开始标记是两个小于号 + Here 文档的名称,名称可以随意取,后面必须是一个换行符;结束标记是单独一行顶格写的 Here 文档名称,如果不是顶格,结束标记不起作用。两者之间就是多行字符串的内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ foo='hello world'
$ cat << '_example_'
$foo
"$foo"
'$foo'
_example_

$foo
"$foo"
'$foo'

Here 文档的开始标记(_example_)放在单引号之中,导致变量替换失效了。

5.Here字符串

Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<)表示。

1
<<< string

它的作用是将字符串通过标准输入,传递给命令。


Bash变量

简介

Bash变量分成环境变量和自定义变量两类

环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。

env命令或printenv命令,可以显示所有环境变量。

下面是一些常见的环境变量。

  • BASHPID:Bash 进程的进程 ID。
  • BASHOPTS:当前 Shell 的参数,可以用shopt命令修改。
  • DISPLAY:图形环境的显示器名字,通常是:0,表示 X Server 的第一个显示器。
  • EDITOR:默认的文本编辑器。
  • HOME:用户的主目录。
  • HOST:当前主机的名称。
  • IFS:词与词之间的分隔符,默认为空格。
  • LANG:字符集以及语言编码,比如zh_CN.UTF-8
  • PATH:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
  • PS1:Shell 提示符。
  • PS2: 输入多行命令时,次要的 Shell 提示符。
  • PWD:当前工作目录。
  • RANDOM:返回一个0到32767之间的随机数。
  • SHELL:Shell 的名字。
  • SHELLOPTS:启动当前 Shell 的set命令的参数,参见《set 命令》一章。
  • TERM:终端类型名,即终端仿真器所用的协议。
  • UID:当前用户的 ID 编号。
  • USER:当前用户的用户名。

很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。

注意,Bash 变量名区分大小写,HOMEhome是两个不同的变量。

创建变量

用户创建变量的时候,变量名必须遵守下面的规则。

  • 字母、数字和下划线字符组成。
  • 第一个字符必须是一个字母或一个下划线,不能是数字。
  • 不允许出现空格和标点符号。

变量声明的语法如下:

1
2
3
variable=value
#如果变量的值包含空格,则必须将值放在引号中
myvar="hello world"

Bash没有数据类型的概念,所有的变量值都是字符串

自定义变量的例子

1
2
3
4
5
6
a=z                     # 变量 a 赋值为字符串 z
b="a string"            # 变量值包含空格,就必须放在引号里面
c="a string and $b"     # 变量值可以引用其他变量的值
d="\t\ta string\n"      # 变量值可以使用转义字符
e=$(ls -l foo.txt)      # 变量值可以是命令的执行结果
f=$((5 * 7))            # 变量值可以是数学运算的结果

3.读取变量

读取变量的时候,直接在变量名前加上$就可以了

如果变量不存在,Bash不会报错,而会输出空字符.

如果变量的值本身也是变量,可以使用${!varname}的语法,读取最终的值.

$ myvar=USER
$ echo ${!myvar}
ruanyf

4.删除变量

unset命令用来删除一个变量。

删除一个变量,也可以将这个变量设成空字符串。

5.输出变量,export命令

export命令用来向子shell输出变量.

1
2
NAME=foo
export NAME

6.特殊变量

Bash提供了一些特殊变量,这些变量的值由Shell提供,用户不能进行赋值.

  1. $?:上一个命令的退出码,用来判断上一个命令是否执行成功. 0 为成功,非0,则失败
  2. $$:为当前Shell的进程ID

这个图书编了可以用来命名临时文件.

LOGFILE=/tmp/output_log.$$

  1. $_:上个命令的最后一个参数

  2. $!为最近一个后台执行的异步命令的进程ID

  3. $0:为当前Shell的名称(在命令行直接执行时)或脚本名(在脚本中执行时)

  4. $-:为当前Shell的启动参数

  5. $@$#表示脚本的参数数量

7.变量的默认值

Bash提供四个特殊语法,跟变量的默认值有关,目的是保证变量不为空.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#如果变量varname存在且不为空,则返回它的值,否则返回word.
${varname:-word}

#如果变量vaname存在且不为空,则返回它的值,否则将它设为word
${varname:=word}

#如果变量名存在且不为空,则返回woord,否则返回空.
${varname:+word}

#如果变量varname存在且不为空,则返回它的值,否则打印出varname:message,并终端脚本的执行
${varname:?message}

8.declare命令

declare命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量

语法如下

declare OPTION VARIABLE=value

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -l:声明变量为小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量。
  • -u:声明变量为大写字母。
  • -x:该变量输出为环境变量。

declare命令如果用在函数中,声明的变量只在函数内部有效,等同于local命令

不带任何参数时,declare命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set命令

9.readonlymingling

readonly命令等同于declare -r,用来声明只读变量,不能改变变量值,也不能unset变量。

1
2
3
4
5
$ readonly foo=1
$ foo=2
bash: foo:只读变量
$ echo $?
1

更改只读变量foo会报错,命令执行失败。

readonly命令有三个参数。

  • -f:声明的变量为函数名。
  • -p:打印出所有的只读变量。
  • -a:声明的变量为数组。

10.let命令

let命令声明变量时,可以直接执行算数表达式

1
2
3
$ let foo=1+2
$ echo $foo
3

字符串操作

1. 字符串的长度

获取字符串长度的语法

1
${#varname}

大括号{}是必需的,否则 Bash 会将$#理解成脚本的参数个数,将变量名理解成文本。

2.子字符串

字符串提取子串的语法

1
${varname:offset:length}

上面语法的含义是返回变量$varname的子字符串,从位置offset开始(从0开始计算),长度为length

1
2
3
$ count=frogfootman
$ echo ${count:4:4}
foot

3.搜索和替换

Bash 提供字符串搜索和替换的多种方法。

(1)字符串头部的模式匹配。

1
2
3
4
5
6
7
$ myPath=/home/cam/book/long.file.name

$ echo ${myPath#/*/}
cam/book/long.file.name

$ echo ${myPath##/*/}
long.file.name

(2)字符串尾部的模式匹配。

(3)任意位置的模式匹配。

4.改变大小写

下面的语法可以改变变量的大小写。

1
2
3
4
5
# 转为大写
${varname^^}

# 转为小写
${varname,,}

Bash算术运算

1.算术表达式

((...))语法可以进行整数的算术运算。

1
2
3
$ ((foo = 5 + 5))
$ echo $foo
10

((...))语法支持的算术运算符如下。

  • +:加法
  • -:减法
  • *:乘法
  • /:除法(整除)
  • %:余数
  • **:指数
  • ++:自增运算(前缀或后缀)
  • --:自减运算(前缀或后缀)

    1
    2
    3
    4
    
    $ echo $((0xff))
    255
    $ echo $((2#11111111))
    255

3.位运算

$((...))支持以下的二进制位运算符。

  • <<:位左移运算,把一个数字的所有位向左移动指定的位。
  • >>:位右移运算,把一个数字的所有位向右移动指定的位。
  • &:位的“与”运算,对两个数字的所有位执行一个AND操作。
  • |:位的“或”运算,对两个数字的所有位执行一个OR操作。
  • ~:位的“否”运算,对一个数字的所有位取反。
  • !:逻辑“否”运算
  • ^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。

4.逻辑运算

$((...))支持以下的逻辑运算符。

  • <:小于
  • >:大于
  • <=:小于或相等
  • >=:大于或相等
  • ==:相等
  • !=:不相等
  • &&:逻辑与
  • ||:逻辑或
  • expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3

如果逻辑表达式为真,返回1,否则返回0

1
2
3
4
$ echo $((3 > 2))
1
$ echo $(( (3 > 2) || (4 <= 1) ))
1

三元运算符执行一个单独的逻辑测试。它用起来类似于if/then/else语句。

1
2
3
4
5
$ a=0
$ echo $((a<1 ? 1 : 0))
1
$ echo $((a>1 ? 1 : 0))
0

5.赋值运算

算术表达式$((...))可以执行赋值运算。

1
2
3
4
$ echo $((a=1))
1
$ echo $a
1

6.求值运算

逗号,$((...))内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。

1
2
3
4
$ echo $((foo = 1 + 2, 3 * 4))
12
$ echo $foo
3

7.expr命令

expr命令支持算术运算,可以不使用((...))语法。

1
2
$ expr 3 + 2
5

目录堆栈

1.cd-

Bash 可以记忆用户进入过的目录。默认情况下,只记忆前一次所在的目录,cd -命令可以返回前一次的目录。

1
2
3
4
5
# 当前目录是 /path/to/foo
$ cd bar

# 重新回到 /path/to/foo
$ cd -

用户原来所在的目录是/path/to/foo,进入子目录bar以后,使用cd -可以回到原来的目录。

2.pushd,popd

pushd命令时,会将当前目录先放入堆栈,然后将所要进入的目录也放入堆栈,位置在前一个记录的上方。以后每次使用pushd命令,都会将所要进入的目录,放在堆栈的顶部。

popd命令不带有参数时,会移除堆栈的顶部记录,并进入新的堆栈顶部目录(即原来的第二条目录)。

3. dirs

操作目录堆栈的内容


Bash脚本入门

脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。所有能够在命令行完成的任务,都能够用脚本完成。

1.Shebang行

脚本的第一行通常是指定解释器,也就是通过什么解释器执行.

#!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

1
2
3
#!/bin/sh
# 或者
#!/bin/bash

如果 Bash 解释器不放在目录/bin,脚本就无法执行了。为了保险,可以写成下面这样。

#!/usr/bin/env bash

2.执行权限和路径

前面说过,只要指定了 Shebang 行的脚本,可以直接执行。这有一个前提条件,就是脚本需要有执行权限。可以使用下面的命令,赋予脚本执行权限

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 给所有用户执行权限
$ chmod +x script.sh

# 给所有用户读权限和执行权限
$ chmod +rx script.sh
# 或者
$ chmod 755 script.sh

# 只给脚本拥有者读权限和执行权限
$ chmod u+rx script.sh

脚本的权限通常设为755(拥有者有所有权限,其他人有读和执行权限)或者700(只有拥有者可以执行)。

除了执行权限,脚本调用时,一般需要指定脚本的路径(比如path/script.sh)。如果将脚本放在环境变量$PATH指定的目录中,就不需要指定路径了。因为 Bash 会自动到这些目录中,寻找是否存在同名的可执行文件。

建议在主目录新建一个~/bin子目录,专门存放可执行脚本,然后把~/bin加入$PATH

export PATH=$PATH:~/bin

命令改变环境变量$PATH,将~/bin添加到$PATH的末尾。可以将这一行加到~/.bashrc文件里面,然后重新加载一次.bashrc,这个配置就可以生效了。

$ source ~/.bashrc

以后不管在什么目录,直接输入脚本文件名,脚本就会执行。

3.env命令

env命令总是指向/usr/bin/env文件,或者说,这个二进制文件总是在目录/usr/bin

#!/usr/bin/env NAME这个语法的意思是,让 Shell 查找$PATH环境变量里面第一个匹配的NAME。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。

env命令的参数如下。

  • -i, --ignore-environment:不带环境变量启动。
  • -u, --unset=NAME:从环境变量中删除一个变量。
  • --help:显示帮助。
  • --version:输出版本信息。

4.注释

Bash 脚本中,#表示注释,可以放在行首,也可以放在行尾。

5.脚本参数

调用脚本的时候,脚本文件名后面可以带有参数

$ script.sh word1 word2 word3

脚本文件内部,可以使用特殊变量,引用这些参数。

  • $0:脚本文件名,即script.sh
  • $1~$9:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。

如果多个参数放在双引号里面,视为一个参数。

6.shift命令

shift命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1),使得后面的参数向前一位,即$2变成$1$3变成$2$4变成$3,以此类推。

while循环结合shift命令,也可以读取每一个参数。]

1
2
3
4
5
6
7
8
9
#!/bin/bash

echo "一共输入了 $# 个参数"

while [ "$1" != "" ]; do
  echo "剩下 $# 个参数"
  echo "参数:$1"
  shift
done

shift命令可以接受一个整数作为参数,指定所要移除的参数个数,默认为1

7.getopts命令

getopts命令用在脚本内部,可以解析复杂的脚本命令行参数,通常与while循环一起使用,取出脚本所有的带有前置连词线(-)的参数。

getopts optstring name

它带有两个参数。第一个参数optstring是字符串,给出脚本所有的连词线参数。比如,某个脚本可以有三个配置项参数-l-h-a,其中只有-a可以带有参数值,而-l-h是开关参数,那么getopts的第一个参数写成lha:,顺序不重要。注意,a后面有一个冒号,表示该参数带有参数值,getopts规定带有参数值的配置项参数,后面必须带有一个冒号(:)。getopts的第二个参数name是一个变量名,用来保存当前取到的配置项参数,即lha

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
while getopts 'lha:' OPTION; do
  case "$OPTION" in
    l)
      echo "linuxconfig"
      ;;

    h)
      echo "h stands for h"
      ;;

    a)
      avalue="$OPTARG"
      echo "The value provided is $OPTARG"
      ;;
    ?)
      echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
      exit 1
      ;;
  esac
done
shift "$(($OPTIND - 1))"

while循环不断执行getopts 'lha:' OPTION命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量OPTION保存的是,当前处理的那一个连词线参数(即lha)。如果用户输入了没有指定的参数(比如-x),那么OPTION等于?。循环体内使用case判断,处理这四种不同的情况。

8.配置项参数终止符

变量当作命令的参数时,有时希望指定变量只能作为实体参数,不能当作配置项参数,这时可以使用配置项参数终止符--

1
2
$ myPath="~/docs"
$ ls -- $myPath

9.exit命令

exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。

1
2
3
4
5
# 退出值为0(成功)
$ exit 0

# 退出值为1(失败)
$ exit 1

退出时,脚本会返回一个退出值。脚本的退出值,0表示正常,1表示发生错误,2表示用法不对,126表示不是可执行脚本,127表示命令没有发现。如果脚本被信号N终止,则退出值为128 + N。简单来说,只要退出值非0,就认为执行出错。

10.命令执行结果

命令执行结束后,会有一个返回值。0表示执行成功,非0(通常是1)表示执行失败。环境变量$?可以读取前一个命令的返回值。

利用这一点,可以在脚本中对命令执行结果进行判断。

1
2
3
4
5
6
7
cd $some_directory
if [ "$?" = "0" ]; then
  rm *
else
  echo "无法切换目录!" 1>&2
  exit 1
fi

11.source命令

source命令用于执行一个脚本,通常用于重新加载一个配置文件。

$ source .bashrc

source命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,source命令执行脚本时,不需要export变量。

source有一个简写形式,可以使用一个点(.)来表示。

12.别名,alias命令

alias命令用来为一个命令指定别名,这样更便于记忆。下面是alias的格式。

alias NAME=DEFINITION

1
2
3
$ alias today='date +"%A, %B %-d, %Y"'
$ today
星期一, 一月 6, 2020

alias定义的别名也可以接受参数,参数会直接传入原始命令。

1
2
3
$ alias echo='echo It says: '
$ echo hello world
It says: hello world

unalias命令可以解除别名。

直接调用alias命令,可以显示所有别名。


read命令

脚本需要在执行过程中,由用户提供一部分数据,这时可以使用read命令。它将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。

read命令的格式如下。 read [-options] [variable...]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

echo -n "输入一些文本 > "
read text
echo "你的输入:$text"

###
$ bash demo.sh
输入一些文本 > 你好,世界
你的输入:你好,世界

条件判断

if结构

语法如下

1
2
3
4
5
6
7
if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

test命令

1
2
3
4
5
6
7
8
# 写法一
test expression

# 写法二
[ expression ]

# 写法三
[[ expression ]]

上面的expression是一个表达式。这个表达式为真,test命令执行成功(返回值为0);表达式为伪,test命令执行失败(返回值为1)。注意,第二种和第三种写法,[和]与内部的表达式之间必须有空格。


剩余部分改期继续

点击刷新