Home Shell (2)
Post
Cancel

Shell (2)

[toc]

阮一峰老师写的教程,这里做个精简笔记。

1 引号和转义

1.1 转义

$ & * 在 Bash 中有特殊含义,需要通过转义字符 \ 进行转义后才能原样输出。

换行符也是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。

换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,Bash 会将其当作空格处理,从而可以将一行命令写成多行。

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

# 等同于
$ mv /path/to/foo /path/to/bar
1.2 引号

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

双引号比单引号宽松,大部分特殊字符在双引号里都会失去特殊含义变成普通字符。但是,美元符号、反引号和反斜杠,这三个字符在双引号之中依然会被 Bash 自动扩展。

换行符在双引号中失去特殊含义,Bash 不再将其解释为命令的结束,只是作为普通的换行符。所以可以利用双引号,在命令行输入多行文本。

双引号还有一个作用,就是利用换行符失去特殊含义,可以保存原始命令的输出格式。

1
2
3
4
5
# 单行输出
$ echo $(cal)

# 原始格式输出
$ echo "$(cal)"
1.3 Here 文档/字符串

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

1
2
3
<< token
text
token

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

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat << _EOF_
<html>
<head>
    <title>
    The title of your page
    </title>
</head>

<body>
    Your page content goes here.
</body>
</html>
_EOF_

Here 文档内部会发生变量替换,同时支持反斜杠转义,但是不支持通配符扩展,双引号和单引号也失去语法作用,变成了普通字符。

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

hello world
"hello world"
'hello world'

如果不希望发生变量替换,可以把 Here 文档的开始标记放在单引号之中。

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

$foo
"$foo"
'$foo'

Here 文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了 echo 命令。

1
2
3
4
5
6
7
$ command << token
  string
token

# 等同于

$ echo string | command

所以,Here 字符串只适合那些可以接受标准输入作为参数的命令,对于其他命令无效,比如echo命令就不能用 Here 文档作为参数。

1
2
3
$ echo << _example_
hello
_example_

上面例子不会有任何输出,因为 Here 文档对于 echo 命令无效。

此外,Here 文档也不能作为变量的值,只能用于命令的参数。

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

1
<<< string

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

有些命令直接接受给定的参数,与通过标准输入接受参数,结果是不一样的。所以才有了这个语法,使得将字符串通过标准输入传递给命令更方便,比如 cat 命令只接受标准输入传入的字符串。

2 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 打印所有变量(自定义 + 环境)
$ set
## 打印环境变量
$ env
## 自定义变量,等号两边不能有空格
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))            # 变量值可以是数学运算的结果
## 读取变量,变量前加 $ 即可,如果变量值本身也是变量,可以使用 ${!varname} 的语法,读取最终值
$ myvar=USER
$ echo ${!myvar}
cy
## 删除变量
unset NAME

用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用 export 命令。这样输出的变量,对于子 Shell 来说就是环境变量。

2.1 特殊变量
  • $?,上一个命令的退出码,用于判断之前的命令是否执行成功。0 为成功,非 0 为失败

  • $$,当前 Shell 进程 id

  • $_,上一命令的最后一个参数

  • $!,最近一个后台执行的异步命令进程 id

    1
    2
    3
    4
    5
    
    $ firefox &
    [1] 11064
        
    $ echo $!
    11064
    
  • $0,当前 Shell 名称

  • $-,当前 Shell 启动参数

  • $@ $#,脚本参数数量

2.2 变量默认值

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

1
2
3
4
5
6
7
8
9
10
11
## varname 存在且不为空,则返回它的值,否则返回 word。目的是返回一个默认值
${varname:-word}

## varname 存在且不为空,则返回它的值,否则将它设为 word,并且返回 word。目的是设置变量的默认值
${varname:=word}

## 变量名存在且不为空,则返回 word,否则返回空值。目的是测试变量是否存在
${varname:+word}

## varname 存在且不为空,则返回它的值,否则打印出 varname: message,并中断脚本的执行。如果省略了 message,则输出默认的信息 “parameter null or not set.”。目的是防止变量未定义
${varname:?message}

上面四种语法如果用在脚本中,变量名的部分可以用数字 19,表示脚本的参数。

1
2
## 1 表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
filename=${1:?"filename missing."}

3 字符串

1
2
3
4
5
6
7
## 字符串长度
${#varname}

## 子字符串,不能直接操作字符串,只能通过变量来读取字符串,并且不会改变原始字符串
${varname:offset:length}

## offset 为负数时,表示从末尾开始计算

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

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
## 字符串头匹配
# 如果 pattern 匹配变量 variable 的开头,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable#pattern}

# 如果 pattern 匹配变量 variable 的开头,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable##pattern}

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

$ path=/home/cam/book/long.file.name
$ echo ${path##*/}
long.file.name

$ phone="555-456-1414"
$ echo ${phone#*-}
456-1414
$ echo ${phone##*-}
1414

## 匹配不成功,返回原始字符串
$ phone="555-456-1414"
$ echo ${phone#444}
555-456-1414

## 字符串头替换
# 模式必须出现在字符串的开头
${variable/#pattern/string}
# 示例
$ foo=JPG.JPG
$ echo ${foo/#JPG/jpg}
jpg.JPG

## 字符串尾匹配
# 如果 pattern 匹配变量 variable 的结尾,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable%pattern}

# 如果 pattern 匹配变量 variable 的结尾,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable%%pattern}

## 字符串尾替换
# 模式必须出现在字符串的结尾
${variable/%pattern/string}
# 示例
$ foo=JPG.JPG
$ echo ${foo/%JPG/jpg}
JPG.jpg

## 任意位置匹配
# 如果 pattern 匹配变量 variable 的一部分,
# 最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配
${variable/pattern/string}

# 如果 pattern 匹配变量 variable 的一部分,
# 最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换
${variable//pattern/string}

$ path=/home/cam/foo/foo.name
$ echo ${path/foo/bar}
/home/cam/bar/foo.name
$ echo ${path//foo/bar}
/home/cam/bar/bar.name

# : 换成换行符
$ echo -e ${PATH//:/'\n'}
/usr/local/bin
/usr/bin
/bin
...

大小写

1
2
3
4
5
6
7
8
9
10
11
# 转为大写
${varname^^}

# 转为小写
${varname,,}

$ foo=heLLo
$ echo ${foo^^}
HELLO
$ echo ${foo,,}
hello

4 算术运算

((...)) 语法可以进行整数的算术运算,这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是 0,命令就算执行成功。

1
2
3
4
5
6
7
8
9
10
$ (( 3 + 2 ))
$ echo $?
0

$ (( 3 - 3 ))
$ echo $?
1

$ echo $((2 + 2))
4

Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数(以10为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#numberbase进制的数。
1
2
3
4
$ echo $((0xff))
255
$ echo $((2#11111111))
255

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

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

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

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

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

5 行操作

Bash 内置了 Readline 库,具有这个库提供的很多“行操作”功能,比如命令的自动补全,可以大大加快操作速度。

这个库默认采用 Emacs 快捷键,也可以改成 Vi 快捷键。

1
2
$ set -o vi
$ set -o emacs

光标移动

  • Ctrl + a:移到行首
  • Ctrl + e:移到行尾
  • Alt + b:移动到当前单词的词首
  • Alt + f:移动到当前单词的词尾

Alt 键,可以用 ESC 键代替。

编辑操作

  • Ctrl + w:删除光标前面的单词
  • Ctrl + t:光标位置的字符与它前面一位的字符交换位置(transpose)
  • Alt + t:光标位置的词与它前面一位的词交换位置(transpose)

自动补全

  • Tab:完成自动补全
  • Alt + ?:列出可能的补全,与连按两次 Tab 键作用相同
  • Alt + /:尝试文件路径补全

License:署名-相同方式共享 3.0

This post is licensed under CC BY 4.0 by the author.