Post

shell script 学习笔记(一)

本文主要受益于《鸟哥的 Linux 私房菜》。

shell 中有一个很有意思的变量 PS1,用于规定提示字符的形式,详见 Bash PS1 customization examples

shell 变量

变量定义

bash 中变量分为两种:环境变量与自定义变量。

定义自定义变量:

1
$ myname=XT

这样就定义了一个自定义变量 XT,注意 = 两边不能有空格!

若要定义环境变量,则需要使用 export 命令将自定义变量变为环境变量:

1
$ export myname=XT

这样就定义了一个环境变量 myname,它的值为 XT

要删除一个变量也很简单,使用 unset 命令就可以:

1
$ unset myname

这样就删除了变量 myname

变量作用域的问题

这两种变量主要区别在于作用域,其中环境变量对所有子进程可见,而自定义变量则只对本进程可用。这是因为子进程只会继承父进程的环境变量,而不会继承父进程的自定义变量。

变量操作

变量的普通赋值很容易,模式为 var=value,其中 value 为常量,且会被认为是字符串。

然而当遇到需要从现有变量中构造出新的变量,下面这些模式就显得非常有用:

变量截取

变量设置方式说明
${变量#关键词}若【变量】内容从头开始的数据符合【关键词】,则将符合的最短数据删除
${变量##关键词}若【变量】内容从头开始的数据符合【关键词】,则将符合的最长数据删除
${变量%关键词}若【变量】内容从尾向前的数据符合【关键词】,则将符合的最短数据删除
${变量%%关键词}若【变量】内容从尾向前的数据符合【关键词】,则将符合的最长数据删除
${变量/旧字符串/新字符串}若变量内容满足【旧字符串】则第一个【旧字符串】会被【新字符串】替换
${变量//旧字符串/新字符串}若变量内容满足【旧字符串】则全部的【旧字符串】会被【新字符串】替换

变量选择性赋值方式

名字我瞎起的,大概知道什么意思就行,下面是模式:

变量设置方式str 没有设置str 为空字符str 已设置为非空字符串
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=expr
var=${str:-expr}var=var=var=expr
var=${str=expr}str=expr
var=expr
str=不变
var=expr
str 不变
var=$str
var=${str:=expr}str=expr
var=expr
str=expr
var=expr
str 不变
var=$str
var=${str?expr}expr 输出至 stderrvar=var=$str
var=${str:?expr}expr 输出至 stderrexpr 输出至stderrvar=$str

输出重定向

输出重定向主要是通过 >>> 将原本输出到 stdoutstderr 的信息输出到指定的文件中。其中 stdout 的文件描述符为 1stderr 的文件描述符为 2,借助输出重定向我们也可以将原本都输出到终端的 stdoutstderr 信息分开。比如:

1
./something.sh 1> stdout.txt 2> stderr.txt

这是比较基本的操作。我写这一部分主要是为了记录一种文件描述符绑定的方式,比如我现在要将 stderr 的输出内容也输出到 stdout,可以用以下的命令:

1
2
3
4
5
# correct
./something.sh 1> stdout.txt 2>&1

# wrong, 会被 bash 认为将 stderr 的信息输出到文件名为 1 的文件中
./something.sh 1> stdout.txt 2> 1

这里多了一种特殊语法 &,含义为告诉 bash,后面跟着的不是文件名,而是一个文件描述符。详见 StackOverflow

一些有用的命令

uniq

为单词 unique 的简写。顾名思义,可以消除相同结果的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# echo without uniq
$ echo -e "test\ntest"
> test
> test

$ echo -e "test\ntest" | uniq
> test

# 可以使用 -i 选项来忽略大小写的不同
$ echo -e "test\nTEST" | uniq -i
> test

# 同时还可以使用 -c 选项来对重复的内容进行计数
$ echo -e "test\ntest\nTEST\n" | uniq -c
> 2 test
> 1 TEST

# 但是貌似只能合并相邻
$ echo -e "test\nTEST\ntest" | uniq -c
> 1 test
> 1 TEST
> 1 test

由上例可以看出 uniq 只能相邻内容的重复,那若想要不受相邻的限制呢?可以使用 sort 命令先对内容进行排序。sort 命令的详细用法参见 man page

tee

tee 命令用于双向重定向。双向重定向也就是把输出内容重定向到两个目的地。比如在运行一个程序的时候既想把结果输出到终端上,也想保存在某个文件中,这时候就可以使用 tee 命令:

1
2
3
4
5
# 输出 ls -l 结果,同时保存在 files.txt 中(覆写)
ls -l | tee files.txt

# 输出 ls -l 结果,同时保存在 files.txt 中(追加)
ls -l | tee -a files.txt

xargs

xargs 命令用于产生某个命令的参数。是不是感觉根本不知道什么意思?我也差不多:),也只能理解个大概使用方法,详细描述参见 man page。如果你的目的和我一样只要略知一二即可,不如直接来看例子:

1
2
3
4
5
# 如果我们想列出当前文件夹下的文件详细信息,我们会使用 ls -l 命令。但如果我们只想要列出权限为 644 的文件信息呢?
# 一种方法是使用管道命令,先使用 ls -l 将所有结果列出来,然后通过管道将结果输送给 grep,再由 grep 找出权限为 644 的文件再加以输出。
ls -al | grep .*rw-r--r--
# 另一种方式就是使用 xargs。可以先用 find 命令找出所有权限为 644 的文件,再使用 xargs 将这些文件变成 ls -l 指令的参数。
find . -maxdepth 1 -perm 644 | xargs ls -l

bash 通配符

需要与后面的正则表达式区分开:

符号意义
*代表【0 个到无穷多个】任意字符
?代表【一定有一个】任意字符
[]同样代表【一定有一个在括号内】的字符(非任意字符)
[-]若有减号在中括号内时,代表【在编码顺序内的所有字符】
[^]若中括号内第一个字符为^,就代表【反向选择】

基础正则表达式

特殊符号

bash 下正则表达式的特殊符号:

特殊符号代表意义
[:alnum:]代表英文大小写字符及数字
[:alpha:]代表英文大小写字符
[:blank:]代表空格与制表符 \t
[:cntrl:]代表控制按键,包括 CR、LF、Tab、Del 等
[:digit:]代表数字
[:graph:]代表除了空格和制表符 \t 之外的所有按键
[:lower:]代表小写字符
[:upper:]代表大写字符
[:print:]代表任何可以打印出来的字符
[:punct:]代表标点符号,即 ;:?!’”#$
[:space:]代表所有会产生空格的符号,包括 空格,制表符 \t 和 CR
[:xdigit:]代表 16 进制的符号,包括 0-9, a-f, A-F

自定义字符集合

类似于 bash 的通配符,bash 正则表达式也使用中括号 [] 来选择字符集合:

1
2
# 在 sample.txt 中寻找包含 tast 或 test 的行
$ grep -n 't[ea]st' sample.txt

同样的也有反选机制,也是用 ^ 来标识:

1
2
# 在 sample.txt 中寻找包含字符 foo,但 foo 之前没有字符 g 的行
$ grep -n '[^g]foo' sample.txt

行首行尾匹配

bash 正则表达式用于匹配行首的字符为 ^,用于匹配行尾的字符为 $

看似好像这里的 ^ 和自定义字符集合的 ^ 重复了对吧。但其实区别在于 ^ 出现的位置。若 ^ 出现在中括号内就代表反选,否则就代表匹配行首。

1
2
3
4
5
6
7
8
# 在 sample.txt 中查找以单词 the 开头的行
$ grep -n '^the' sample.txt

# 在 sample.txt 中查找以单词 the 结尾的行
$ grep -n 'the$' sample.txt

# 找出 sample.txt 中的空行
$ grep -n '^$' sample.txt

任意字符 . 与重复字符 *

bash 正则表达式中,. 代表任意字符(同时也有一定有一个字符的意思),而 * 代表重复前一个字符,0 次或无穷次

这个很好理解就不给例子了。

限定连续字符数量

bash 正则表达式中可以使用 {} 来限定连续字符的数量。比如:

1
2
3
4
5
6
7
8
# 在 sample.txt 中找出存在连续的 2-5 个 O 的行
$ grep -n 'O\{2,5\}' sample.txt

# 在 sample.txt 中找出存在连续的 2 个 O 的行
$ grep -n 'O\{2\}' sample.txt

# 在 sample.txt 中找出存在大于连续的 2 个 O 的行
$ grep -n 'O\{2,\}' sample.txt

扩展正则表达式

查找多个模式

可以使用 | 符号将多个正则表达式组合成 or 的含义:

1
2
# 在 sample.txt 中找出以 # 开头的行以及空行
$ grep -n '^#|^$' sample.txt

+?

+ 符号类似于 *,会匹配重复 一个或多个以上 的前一个字符。

? 符号会匹配 0 个或 1 个 前一个字符。

群组功能

使用 () 实现群组功能。比如我想查找包含字符串 glad 和 good 的行:

1
$ grep -n 'g(la|oo)d' samples.txt

类似于普通的单个字符,群组后面也可以加 +, *, ?, {} 等数量限定符。

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