本文主要基于《实用Linux Shell编程》总结,并加入一些网上查询资料和博主自己的推断。
其中命令相关的,已抽取出来在另一篇系统性学习】Linux Shell常用命令中,可以一起使用。
文章目录
-
- 一、基础知识
- 二、命令与环境
- 三、变量和数组
- 四、条件流程控制
- 五、循环
- 六、函数
- 七、通配符、正则表达和文本处理
- 八、进程与作业
- 九、其他话题
- 十、Bash调试
一、基础知识
- Linux 系统主要目录及简单描述


目录 |
描述 |
/bin |
bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令 |
/boot |
内核及其他系统启动时需要的文件,包括一些连接文件以及镜像文件 |
/dev |
dev 是 Device(设备) 的缩写, 存放的Linux 的外部设备,Linux把所有外设都看做是一个文件,对文件的操作就是对外部设备的操作 |
/etc |
etc 是 Etcetera(等等) 的缩写,用来存放所有的系统管理所需要的配置文件,该目录及子目录下有很多.conf文件 |
/home |
用户的主目录,系统默认的普通用户主目录为/home/ ,如上图中的 alice、bob 和 eve,保存用户自己的配置文件、文档、数据等 |
/lib |
lib 是 Library(库) 的缩写,存放着系统最基本的动态连接共享库文件(由/bin和/sbin使用),其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。/user/lib中包含更多用于用户程序的库文件 |
/lost+found |
这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件,用于系统修复和恢复 |
/mnt |
系文件系统挂载点,一般用于安装移动介质,其他文件系统的分区、网络共享文件系统或者任何可安装的文件系统。比如可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了。 |
/opt |
opt 是 optional(可选) 的缩写,主要由第三方开发者用于安装和卸载他们的软件包。比如你安装一个ORACLE数据库或者spark就可以放到这个目录下。默认是空的。 |
/proc |
proc 是 Processes(进程) 的缩写,是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息(如 /proc/version)。这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件,比如可以通过下面的命令来屏蔽主机的ping命令,使别人无法ping你的机器:echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all |
/root |
该目录为系统管理员,也称作超级权限者的默认主目录。 |
/sbin |
s就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是超级用户管理系统时使用的系统管理程序。普通用户几乎没有权限执行这里面的命令。/user/sbin是超级用户使用的比较高级的管理程序和系统守护程序 |
/tmp |
tmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。当系统重启时,改目录的文件会被自动清空 |
/usr |
usr 是 unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。 |
/var |
var 是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。 |
- ls -l 详情解释:
-rw-------. 1 root root 1.3K Dec 14 01:38 anaconda-ks.cfg
dr-xr-xr-x. 17 root root 224 Dec 14 01:38 bin
- 第一列共10位,第1位表示文档类型,d表示目录,-表示文件,l表示链接文件,d表示可随机存取的设备,如U盘等,c表示一次性读取设备,如鼠标、键盘等。后9位,依次对应三种身份所拥有的权限,身份顺序为:owner、group、others,权限顺序为:readable、writable、excutable
- 第二列,对于文件表示硬链接数,表示有多少个文件链接到inode号码。对于目录表示子目录数(包括隐藏文件),所有目录都包含".“和”…"两个隐藏目录,所以目录的第二列
>=2
。
- 第三列表示拥有者,user
- 第四列表示所属群组,group
- 第五列表示文档容量大小
- 第六列表示文档最后修改时间,注意不是文档的创建时间
- 第七列表示文档名称。以点(.)开头的是隐藏文档
- 账户权限说明:对于文件,读:可以查看 写:可以修改 执行:可以运行。对于目录,读:可以浏览,用ls 写:可以创建和删除文件 执行:可以进入该目录。
- SUID:有的可执行二进制文件的账户权限为
rws
,s是SUID(即set uid),表示其他账户在执行时,有文件所有者的权限。如passwd命令会改/etc/shadow文件,这个文件只有root用户可以修改。但其他账户同样可以执行passwd修改该文件。原因就是passwd文件是s
权限。chmod u+s file
或chmod 4755 file
添加该权限。rwS
显示S是不正常的,给文件添加x权限才会正常。SUID仅对可执行二进制文件起作用。
- SGID:有的可执行二进制文件的所属组权限为
rws
,,s是SGID(即set gid)表示其他账户在执行时,具有文件所属组的权限。对目录设置SGID,则作用为:任何账户如果可以在该目录内建立新文件或新的子目录,那么新文件和子目录的所属组与该目录的所属组保持一致。SGID属性要保证目录或文件对所属组由x权限,否则为大写S。chmod g+s file
或chmod 2755 file
添加该权限
- SBIT:粘滞位,t是SBIT只有目录可以设置,作用是:在一个大家都有权限的目录下,账户不能删除别人的文件或目录。
chmod o+t file
或chmod 1755 file
添加该权限。SBIT要求其他账户有执行权限,否则为大写T。
二、命令与环境
- linux命令分为内置命令和外部命令:help查看所有内置命令和关键字列表,
help command
查看内置命令详情。外置命令:which command
查看外置命令位置,which无法对内置命令生效。command --help
显示外置命令详情。
- 内置变量PS1,主命令提示参数。重新登陆shell则恢复。永久保持写入
~/.bashrc
中。
\h 计算机名
\t 当前时间
\u 账户名
\w 当前路径
> PS1="\h@\u@\t$"
host-10-31-17-80@user_client@16:17:03$
- 搜索路径PATH,路径用
:
隔开,按顺序先后查找,找到就停止。在原路径后面加路径PATH='$PATH:/tmp/bin'
。
- 要永久改变环境变量,将修改写进~/.bashrc文件中。
- 权限掩码umask
- source和点命令:
souce file
和. file
等价,如果file不在当前目录下,会在$PATH
下找。
- 直接执行脚本文件和source都能执行脚本中的命令。差异:a. 脚本在子shell中运行,而source是在父shell b. 直接运行脚本需要脚本有x权限,source不需要
- 命令解释顺序及改变。命令类型查看用
type command
alias->keywords->function->built-in->$PATH
别名->关键字->函数->内置命令->外部命令
- command <命令> 禁用别名和函数,先处理内置命令和外部命令
- builtin <命令> 只查找内置命令
- enable 禁用(-n)和使能内置命令(尽量少用,要避免自己的编写的命令和内置命令重名)
- 命令或程序结束后会返回一个退出状态,取值0-255,0表示成功执行。内置变量
$?
存了上一条命令的退出状态 。
- 内置命令true用于返回成功的状态,false则是失败。
- 管道
左侧|右侧
,理解成左侧命令的输出,会给到右侧命令的输入,所以右侧命令不需要写代表输入的参数。
- 执行一个shell命令会自动打开标准输入文件(stdin)和标准输出文件(stdout),他们默认对应终端键盘和终端屏幕,分别对应文件描述符0和1。文件描述符(file descriptor,fd)是进程对其打开文件的索引,形式上是个非负整数。
> ls -l /dev/std*
lrwxrwxrwx 1 root root 15 Aug 19 2015 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Aug 19 2015 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Aug 19 2015 /dev/stdout -> /proc/self/fd/1
> ls -l /proc/self/fd/*
lrwx------ 1 user_client users 64 Oct 22 17:00 /proc/self/fd/0 -> /dev/pts/0
lrwx------ 1 user_client users 64 Oct 22 17:00 /proc/self/fd/1 -> /dev/pts/0
lrwx------ 1 user_client users 64 Oct 22 17:00 /proc/self/fd/2 -> /dev/pts/0
Linux下的tty和pts详解
概述Linux TTY/PTS的区别
简单的说,/dev/pts/0 可以当做一个终端,这个终端连接了输入和输出,所以fd/0(标准输入)、fd/1(标准输出)、fd/2(标准错误)都可以连到这个终端上来。
Input +--------------------------+ R/W +------+
----------->| |<---------->| bash |
| pts/1 | +------+
<-----------| |<---------->| lsof |
Output | Foreground process group | R/W +------+
+--------------------------+
- 输入重定向:
命令 输入
->命令 < 文件名(不能是带输出的命令)
- 输出重定向:
命令(能产生输出)
->命令(能产生输出) >文件名
,命令(能产生输出) >>文件名
追加模式。> 文件名
可以创建空文件或者清空文件。输出重定向是不安全的。如果想不覆盖源文件,可开启noclobber选项,set -o noclobber
。
- 标准错误输出(stderr),对应fd/2,普通重定向
>
不起作用。需要 2>
实现重定向。
- 同时处理,
1>file1 2>file2
标准到file1,错误到file2,>file 2>&1
同时输出到file(等价 &> file
和>& file
)。
- “黑洞”,/dev/null一般用作垃圾箱,把不要的输出重定向到这里,如
2> /dev/null
- 一行多命令,用
;
隔开。
- 后台执行:
command &
,执行后会输出 [工作号] 进程号
。
- 续行:命令太长可用
\
来分行,PS2控制续行提示符。
- 特殊文件名处理:空格前面加
\
,-a.txt
这样-开头的,用vi -- -a.txt
或vi ./-a.txt
。因为-后面会被认为是选项。Bash规定--(空格)
后面的东西不是选项,而是文件名或参数。
- 小括号,大括号,中括号
三、变量和数组
- Bash只有字符串类型。整数型字符串赋值被变量时,变量相当于多了个整数属性,可以进行整数运算。
- 变量定义时,等号左右两边不能有空格。引用变量时,前面加
$
。
- 双引号作用,保留空格。
a=
和b=" "
,a是为空的,b才能保留空格。
- 单引号:相当于raw字符串:被单引号括起的内容不管是常量还是变量者不会发生替换。
- 双引号:把双引号内的内容输出出来;如果内容中有命令、变量等,会先把变量、命令解析出结果,发生替换,然后在输出最终内容来。
- 不加引号:不会将含有空格的字符串视为一个整体输出, 如果内容中有命令、变量等,会先把变量、命令解析出结果,然后在输出最终内容来,如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以用。
- 取命令执行结果:`命令`,
$(命令)
都可以取命令输出的结果。要保留换行符、首尾空格等符号,则用"$(命令)"
。可以把()
当做子shell,里面的操作不会对父shell产生影响。少用反引号,因为$()
便于嵌套。注意对比取变量${变量}
和取命令输出结果$(命令)
。
> x=10
> b=$(echo $x)
> echo b=$b x=$10
b=10 x=0
> b=$(x=5;echo $x)
> echo b=$b x=$x
b=5 x=10 --父shell中的x没被改变
> time=$(echo Today is $(date))
> echo $time
Today is Sun Oct 23 14:06:37 CST 2022
> aa=${date}
> echo aa
--空,因为变量date没有定义
> date=3
> t1=$(date)
> t2=$date
> t3=${date}
> echo -e "t1=$t1\nt2=$t2\nt3=$t3"
t1=Sun Oct 23 14:11:21 CST 2022
t2=3
t3=3
> echo "`date`"
Sun Oct 23 14:19:05 CST 2022
> echo "\`date\`"
`date`
> echo "$(date)"
Sun Oct 23 14:19:22 CST 2022
> echo "\$(date)"
$(date)
- 整型计算:放在双括号间或let后面。
$((表达式))
可取出表达式计算结果。单独的((表达式))
仅仅是计算,而没有输出。i++
(先用后加),++i
先加后用,支持+=,-=,*=,/=,%=,^=
等运算。支持算木运算((条件?结果1:结果2))
。$(())
可替换成$[]
,注意,有$时才能替换。另外,declare -i var
定义了整型变量后,var=表达式
不需要括号也可进行整型计算。
w=date
> echo $w
date
> $w
Sun Oct 23 16:03:32 CST 2022
> echo $((50/20))
2
> echo $((w=50/20))
2
> echo $w
2
> echo $[a=10+5]
15
> echo $a
15
>declare -i v
>v=$a+$w
>echo $v
30
>v=a+10
>echo $v
25
- 浮点运算:用bc或者awk。
echo "scale=5;表达式|bc"
或者直接bc
开客户端运算,quit退出。
- 其他进制:bash支持其他进制运算,只要在算数表达式能生效的地方都可以使用。两种书写方法,a.
基数#数值
,基数可取2到64。b. 0开头表示8进制,0x开头表示十六进制:
((w=8#16))
((w=016))
((w=16#25))
((w=0x25))
- 数组:bash只支持一维数组,下标默认从0开始。单独引用数组名时,返回数组第一个元素。打印数组全部详细信息,用
declare -p 数组名
。
y=(aa bb cc)
y=([1]=aa [3]=bb [5]=cc)
>echo $y
aa
> declare -p y
declare -a y='([0]="aa" [1]="bb" [2]="cc")'
echo $y
echo ${y[1]}
echo ${y[*]}
echo ${y[@]}
echo ${#y[*]}
echo ${!y[*]}
y[3]=dd
y[2]=cc2
unset y[1]
unset y
- 关联数组(其他语言里的map):Bash4以上版本才支持。使用和数组类似,定义如下:
declare -A age=([Mike]=12 [Jack]=24 [Tom]=30)
declare -p age
echo ${!age[@]}
echo ${age[@]}
- Bash中的特殊变量。P118
变量 |
含义 |
$0 |
脚本自身的名字,或者shell的名字 |
$N |
脚本或函数的位置参数,$1,$2,...${10} ,注意大于9要用{} 包起来 |
$# |
位置参数个数,不包括$0 |
$* |
所有位置参数(整体作为一个字符串),不包括$0 |
$@ |
所有位置参数(每个作为独立字符串),不包括$0 |
$? |
上一条命令退出状态 |
$$ |
当前shell进程ID |
$! |
最后一个命令的进程ID |
$- |
当前shell的选项,若echo $- 的输出中包含i,则表示是交互式shell,包含C则表示noclobber开启 |
$_ |
上条命令的最后一个参数 |
set --
清除所有位置参数,不包括$0
。
- 父shell与子shell及其进程ID。
- 一些常用内置变量(或者说环境变量)P121
变量 |
含义 |
BASH |
bash的完整路径,默认为/bin/bash |
BASH_ENV |
非交互式分登陆模式中(比如执行shell脚本时),先执行一遍该变量下指定的脚本 |
CDPATH |
命令cd的搜索路径,多个路径用: 隔开,用于减少输入全路径的情况 |
DISTACK |
当前目录栈存放的数组,配合poshd,popd,dirs使用 |
FUNCNAME |
当某函数被调用时,该变量为函数名;实际上它是数组,记录调用链上所有的函数名 |
GLOBIGNORE |
要忽略的通配模式列表,冒号分割,定义了文件名扩展时(通配符模式下)要忽略的文件名集合,本身也支持通配符,例如GLOBIGNORE=a*;b* 会使得ls * 忽略所有a和b开头的文件 |
HISTFILE |
存放命令历史的文件,通常为~/.bash_history |
HISTFILESIZE |
命令历史文件保存的最大行数 |
HISTIGNORE |
不需保存的历史命令序列,多个通配符模式列表组成,由冒号分隔。用冒号隔开。如HISTIGNORE=ls:t*:\& ,将忽略ls命令,和t开头的命令,并且& 表示连续输入的相同命令只被记录一次,赋值时需要用\ 屏蔽其将命令后台挂起含义 |
HOME |
用户主目录,通常为/home/用户名 ,不要改,会影响cd 及cd ~ 的结果 |
LINENO |
脚本中当前行号 |
OLDPWD |
前一个工作目录,cd - 等价于cd $OLDPWD |
OPTARG |
存放getopts参数的值 |
OPTIND |
待处理的下一个getopts参数索引,初始值为1 |
PATH |
外部命令搜索路径,多个以冒号隔开 |
PPID |
父进程(父shell)的进程ID |
PWD |
当前工作目录 |
RANDOM |
0-32767之间一个随机数 |
REPLY |
read不加变量时,变量缓存。select 用户选择项缓存 |
SECONDS |
当前shell的启动时间 |
SHELL |
登陆linux后的默认shell,/bin/bash比较常用 |
SHELLOPTS |
shell的选项,冒号隔开,例:braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor |
SHLVL |
第一次打开一个shell终端,值为1,没进入一层子shell,值增加1 |
TMOUT |
如果该值大于0,当前shell在等待TMOUT秒后没有任何输入就会自动退出 |
BASH_SOURCE |
更稳健地获取自身脚本路径。BASH_SOURCE是数组,不过它的第一个元素是当前脚本的名称。这在source的时候非常有用,因为在被source的脚本中,$0 是父脚本的名称,而不是被source的脚本名称。而BASH_SOURCE就可以派上用场了。还可和FUNAME配合打印哪个脚本调用了哪个函数。 |
BASH_SUBSHELL |
检查当前环境是不是在subshell中,这个值在非subshell中是0;每进入一层subshell就加1 |
- 常用变量赋值表达式。下面的Default可以是变量,取
$变量
。var也可以是0(不能赋值)或者位置变量1,2,3…10(不能赋值)。
表达式 |
含义 |
${var:-Default} |
若var未定义或为空值,则表达式的值为Default,var不变 |
${var:=Default} |
若var未定义或为空值,则表达式的值为Default,var赋值Default |
${var:?Message} |
若var未定义或为空值,则打印Message且后续代码不再执行,var不变。 |
${!pre*} |
匹配之前所有声明的以pre开头的变量 |
${!pre@} |
匹配之前所有声明的以pre开头的变量 |
- 常用内置字符串操作(支持通配符)。sed和awk也有字符串处理函数,但内置操作最快。下表中的str可替换成
数组名[*]
,则表达式对于数组每个元素做处理。expr命令也可处理字符串,支持正则表达式。注意,str必须变量名。
表达式 |
含义 |
${#str} |
字符串str的长度 |
${str:pos} |
从str的pos位置提取子串,到结尾 |
${str:pos:len} |
从str的pos位置提取长度为len的子串 |
${str#glob} |
从str的开头,删除最短匹配的通配符模式glob |
${str##glob} |
从str的开头,删除最长匹配的通配符模式glob |
${str%glob} |
从str的结尾,删除最短匹配的通配符模式glob |
${str%%glob} |
从str的结尾,删除最长匹配的通配符模式glob |
${str/glob/replacement} |
用replacement代替第一个glob,最长匹配 |
${str//glob/replacement} |
用replacement代替所有glob,最长匹配。 |
${str/#glob/replacement} |
用replacement代替第一个包含开头(从字符串开头算起)的glob,最长匹配 |
${str/%glob/replacement} |
用replacement代替第一个包含结尾(从字符串结尾算起)的glob,最长匹配 |
> a=(one on1 tow)
> echo ${a[*]#o*}
ne n1 tow
> echo ${a[*]##o*}
tow
> b=${a[*]##o.*}
> declare -p b
declare -- b="one on1 tow"
> echo ${a[@]##o*}
tow
> c=${a[@]##o*}
> declare -p c
declare -- c="tow"
四、条件流程控制
- 条件判断命令:
test 条件表达式
或[ 条件表达式 ]
,中括号前后一定要有空格。$?
取命令退出结果来看条件是否满足。满足则退出结果为0,否则为1。
- 整型关系运算:
条件表达式 |
含义 |
[ int1 -lt int2 ] |
int1小于int2 |
[ int1 -le int2 ] |
int1小于等于int2 |
[ int1 -gt int2 ] |
int1大于int2 |
[ int1 -ge int2 ] |
int1大于等于int2 |
[ int1 -eq int2 ] |
int1等于int2 |
[ int1 -ne int2] |
int1不等于int2 |
- 字符串关系运算,注意需要加
\
转义,且str最好用""
保护首尾的空格。
条件表达式 |
含义 |
[ str1 == str2 ] |
str1等于str2,也可以用str1=str2 |
[ str1 != str2 ] |
str1不等于str2 |
[ str1 > str2 ] |
字典序,str1大于str2,str1在str2后面 |
[ str1 < str2 ] |
字典序,str1小于str2,str1在str2前面 |
[ -z str ] |
str为空串zero,长度为0 |
[ -n str ] |
str非空not-zero,长度大于0 |
[ str ] |
同上 |
[[ str =~ regex ]] |
str是否包含(子串关系)正则表达式regex,注意=~ 只能在双中括号中使用,若regex用引号括起来,则当做普通字符串。 |
- 文件属性判断,
条件表达式 |
含义 |
[ -a file ] |
file存在 |
[ -e file ] |
file存在,同a |
[ -s file ] |
file存在且非空(字节数大于0) |
[ -d file ] |
file存在且是一个目录 |
[ -f file] |
file存在且是一个普通文件 |
[ -h file] |
file存在且是一个符号链接 |
[ -L file] |
file存在且是一个符号链接,同h |
[ -p file] |
file存在且为已命名管道 |
[ -u file ] |
file存在且设置了SUID |
[ -g file ] |
file存在且设置了SGID |
[ -k file ] |
file存在且设置了粘滞位 |
[ -r file ] |
file存在且当前用户有读权限 |
[ -w file ] |
file存在且当前用户有写权限 |
[ -x file ] |
file存在且当前用户有执行权限,如果是目录,则有进入该目录权限 |
[ -N file ] |
file存在且最后一次读取后有更改 |
[ -O file ] |
file存在且当前用户是该文件所有者 |
[ -N file ] |
file存在且当前用户所属组是该文件所属组 |
[ -t FD] |
文件描述符打开,并指向一个终端 |
[ f1 -nt f2 ] |
f1修改时间比f2新newer than,或f1存在且f2不存在 |
[ f1 -ot f2 ] |
f1修改时间比f2旧older than,或f2存在且f1不存在 |
[ f1 -ef f2 ] |
f1和f2有相同的设备和节点号(互为硬链接) |
- 逻辑与:
[ 条件表达式1 -a 条件表达式2 ]
,逻辑或:[ 条件表达式1 -o 条件表达式2 ]
,逻辑非:[ ! 条件表达式 ]
。test同样。优先级:非、与、或,多条件可用()
,括号最优先。
- 双中括号代替test或
[]
.[[]]
使得P146:
- a.
(,<
等不用加反斜杠;
- b. 保留字符串首尾有空格时,也不需要加双引号
- c. 判断字符串相等或者不等时,右侧支持通配模式。
*
代表0或多个字符,?
代表一个字符
- d. 支持
&&
和 ||
表达 代替 -a
,-o
-
双小括号助力整数条件运算。(())
使用场景:
I. 计算表达式,((表达式))
等价于 let 表达式
。表达式结果不是逻辑0或1的时候,该表达式不报错的退出状态永远是0。
II. 上述表达式可以是整数相关的条件表达式,使得
a. (,<
等不用加反斜杠;
b. >,<,!=
可应用于整数,并且>=,<=,==
也可使用
c. 支持&&
和 ||
表达 代替 -a
,-o
-
命令的与或非:
I. 与:命令1 && 命令2
,命令结果都为0,才为0。若命令1结果为非0,则命令2不再执行。可用来实现命令依赖。
II. 或:命令1 || 命令2
,命令结果有一个为0,则为0。若命令1结果为0,则命令2不再执行。可用来按优先级至少执行一个命令。
III. 非: ! 命令1
,命令结果有一个为0,则为0。若命令1结果为0,则命令2不再执行。可用来按优先级至少执行一个命令。
>[-r a.txt -a -w b.txt ]
>echo $?
0
> x=5;y=10;z=20
>[ \($x -eq 5 -o $y -gt 5\) -a $z -lt 5 ]
> echo $?
0
>[[ ($x -eq 5 || $y -gt 5) && $z -lt 5 ]]
> echo $?
0
>(( ($x == 5 || $y > 5) && $z < 5 ))
> echo $?
0
>[[ $s1 == a* && $s2 != b? ]]
> [ 1 -eq 2 ] && [ 1 -lt 3 ]
> echo $?
1
- 判断变量是否有定义:
[-v 变量名]
或 test -v 变量名
。结果为0则表示有定义。或者set,从结果中找,存在则表示有定义,但这种方式太繁琐。
- if控制结构,可嵌套使用。
if 命令
then
命令1
命令2
...
if
if 命令;then 命令1;命令2...;fi
if 命令
then
命令(命令组)
else
命令(命令组)
fi
if 命令;then 命令(命令组);else 命令(命令组);fi
if 命令1
then
命令(命令组)
elif 命令2
then
命令(命令组)
elif 命令3
then
命令(命令组)
else
命令(命令组)
fi
if cp a.txt b.txt 2>/dev/null
then
echo “copy done”
fi
if cmd1; then cmd2;else cmd3;fi
cmd1 && cmd2 ||cmd3
- case 控制结构,如下模式可以是1个,或者多个,多个用
|
分割,或关系。模式支持正则表达式。如[6-7][0-9]
表示60~79。
case $变量 in
模式1)
命令(命令组);;
模式2)
命令(命令组);;
...
*)
命令(命令组);;
esac
case $score in
100)
echo "Full Mark";;
9[0-9])
echo "Excellent";;
8[0-5] | 8[6-9])
echo "Very Good";;
[6-7][0-9])
echo "Passing";;
*)
echo "Failed,or Error";;
esac
- exit的退出状态默认为前一条命令的退出状态。也可以
exit N
带整型参数,则退出状态为N,这个特点可以配合if(产生exit 不同结果)和case(对不同结果做处理)命令使用。
- here文档和case可模拟select命令的功能。详见常用命令here。
cat << INPUT
choose your role
A)student
B) teacher
INPUT
read var
case $var in
A)
...;;
B)
...;;
*)
echo "error";;
esac
五、循环
- for控制流程,如下“”列表“”可以是字符串字面量,字符串变量,或位置参数枚举
$*
,$@
,或数组枚举${数组名[*]
,${数组名[@]
I. 为字符串时,元素分割符由IFS指定(Internal Filed Seperator,字段分割符),默认为“空白字符”,linux中的"空白字符"包括:空格、\t
、换行\n
、回车\r
(\n
和\r
是不同的:\r
是指 在同一行中, 使光标回到该行的行首。\n
是指 光标转到下一行)。IFS使用后要记得恢复。IFS可以是多个,表示任意一个做分割,如IFS=";:"
,表示:
和;
都是分割符。
II. $*
,$@
的表现受双引号限制,有引号时"$*"
仍是一个整体,"$@"
则各元素分别作为一个整体;没有引号时,$*
,$@
表现一样,每个元素都分别作为一个整体。
III. ${数组名[*]
,${数组名[@]
同上。
IV. [in 列表]部分不是必须的,没有的时候相当于 for 变量 in "#@"
for 变量 [in 列表]
do
命令(命令组)
done
for 变量 [in 列表];do 命令(命令组);done
> OLD_IFS="$IFS"
> IFS="$OLD_IFS"
> for a in 1 2 3;do echo "${a}T";done
1T
2T
3T --不带引号的字符串,触发天然分割,但该分割不是由IFS指定。
> for a in "1 2 3";do echo "${a}T";done
1 2 3T --不触发天然分割和IFS分割
> str2="1 2 3"
> for a in $str2;do echo "${a}T";done
1T
2T
3T --变量引用触发IFS分割,当前分割符为默认的空白字符
>IFS=":"
> for a in "1:2:3";do echo "${a}T";done
1:2:3T
> str="1:2:3"
> for a in $str;do echo "${a}T";done
1T
2T
3T --变量引用触发IFS分割,当前分割符为:
> for a in 1 2 3;do echo "${a}T";done
1T
2T
3T --触发天然分割未受影响
> echo $str
1 2 3 --此时,echo得到的str结果可以看出,IFS分割已触发,分割后的元素用空格连接打印
> for a in $str2;do echo "${a}T";done
1 2 3T --分割符已变,所以即使触发IFS分割,也分割不了
> a=$str
> declare -p str
declare -- str="1:2:3"
> declare -p a
declare -- a="1:2:3"
> echo "1:2"
1:2
所以,可以猜测,IFS只作用于引用变量的时候,即变量引用触发IFS分割,并将变量以IFS字符分割。
in后面只看有多少被分割元素,不触发分割。
>cat test.sh
for a in "$@";do echo $a;done
>test.sh A B C
A
B
C
>cat test.sh
for a in "$*";do echo $a;done
>test.sh A B C
A B C
>若不加双引号, 即 for a in $* 或 $@,结果都如下
>test.sh A B C
A
B
C --可理解成,$*也触发天然分割,被切开了
for i in {1..10}
do
...
done
- 算数for循环,三个表达式不是必须的,但
;
需要保留。循环条件为空时,表示无限循环。
for ((变量初始化;循环条件;变量值更新))
do
命令(命令组)
done
for ((i=0;i<10;i++));do echo $i;done
i=0
for ((;i<10;));do echo $i;((i++));done
- while循环。先判断while后面命令的退出状态,为0时,do后面的命令再执行。
while 命令
do
命令(命令组)
done
- until循环。先判断while后面命令的退出状态,为0时,do后面的命令再停止。
until 命令
do
命令(命令组)
done
- break和continue可以跳出循环,包括for、while、until。
break N
和continue N
跳出N层循环。技巧if 命令;then break/continue;fi
可写成命令 && break/continue
。
- 循环的重定向以及和管道的配合。循环结构for、while、until都是以done结尾的,done后面可以接
<
,>
做输入输出重定向,或者接| 命令
做管道。因为while和until后面是命令,所以命令就有可能需要有输入,那么也是可以在前面加管道的,即 命令 | while 需要输入的命令
。
for i in c b a m p
do
echo $i
done | sort
while read line
do
echo $line
done < test.txt
cat test.txt|while read line
do
echo $line
done
cat test.txt|while read line
do
echo $line
done > out.txt
六、函数
- 函数定义有三种形式,可接受位置参数,调用时用法和脚本一样
function 函数名
{
命令(命令组)
}
函数名()
{
命令(命令组)
}
function 函数名()
{
命令(命令组)
}
function 函数名 {命令(命令组);}
函数名 arg1 arg2 arg3
source 函数定义脚本
$(函数名)
$(函数名 arg1 agr2)
- 函数内变量,如果和全局变量同名,则为全局变量,否则需要加上
local
或者declare
声明成局部变量;如果是非同名变量,则为局部变量,加上declare -g
则可声明成全局变量。
- 当前函数名,存在内部变量FUNNAME中。FUNNAME是一个数组,第一个元素为当前函数名,第二个元素为上一层调用函数名,
declare - p FUNNAME
或 echo ${FUNAME[*]}
查看调用链。
- 函数的导出与清除。
declare -fx fun1
, 导出函数,等同 export -f fun1
。清除 unset -f 函数名
或unset 函数名
。declare -f fun1
查看函数定义。
- 函数返回命令return, 可带参数N。执行return后,函数中后续命令不再执行。return不带参数时,函数的返回状态为return上一条命令的状态。
retrun N
时,,函数的返回状态为N。可当做默认函数结尾有个隐含的return。
- 递归函数,可以实现,但运行效率较低,且占用系统资源较多,不提倡写递归函数。
七、通配符、正则表达和文本处理
- 通配符模式,glob(也叫wildcard)。支持以下几种
通配符 |
含义 |
* |
表示任意字符 |
? |
表示一个字符 |
[] |
范围替换[abc] 表示a或b或c,[0-9] ,[a-z] 前面的字符不能大于后面的字符。[A-Z0-9] 组合在一起也可以。 |
[!] |
不在范围内,[!abc] 表示不为a或b或c,[!0-9] 表示不为0-9 |
- 扩展通配模式,可通过
shopt -s extglob
打开扩展(-u 表示关闭),支持:
通配模式 |
含义 |
*(pattern) |
匹配所给模式零次或多次,如*(m) ,表示0个或多个m |
?(pattern) |
匹配所给模式零次或一次 |
+(pattern) |
匹配所给模式一次或多次 |
@(pattern) |
匹配所给模式仅一次 |
!(pattern) |
不匹配所给模式 |
- 通配符模式使用场景
I. shell命令行里,加单引号,双引号或反斜杠时,失去通配符能力。如ls "*"
找的就是名为*
的文件。
II. find -name file
,中file支持通配符,且本身就放在双引号或单引号中,如find -name "a*.txt"
,通配符能力不失去,加反斜杠才失去通配能力。
III. 条件判断双中括号[[]]
的等号或不等号右边支持通配符,右边如果带双引号或单引号或反斜杠,通配符失效。
IV. 第三章15节的常用内置字符串操作(支持通配符)。如果带双引号或单引号或反斜杠,通配符失效。
> [[ "ads" == a* ]]
> echo $?
0
> [[ "ads" == "a*" ]]
> > echo $?
1 --双引号,通配符失效
> str="abcd"
> echo ${str/a*/K}
K
> echo ${str/"a*"/K}
abcd --双引号,通配符失效
- GLOBIGNORE,被忽略的通配模式,用冒号分割。仅作用于命令行。
> touch a.txt b.txt c.txt d.txt
> find -name "*"
.
./c.txt
./a.txt
./d.txt
./b.txt
> ls *
a.txt b.txt c.txt d.txt
> GLOBIGNORE=a*:b*
> ls *
c.txt d.txt
> find -name "*"
.
./c.txt
./a.txt
./d.txt
./b.txt
- 正则表达式,各语言的正则表达式支持范围略有不同,具体可查阅【中文,英文】。linux中,平常的正则表达式简称BRE(Basic Regular Expressions),扩展的正则表达式叫ERE(Extended Regular Expressions)。Linux里的正则表达式都是贪婪的(当可匹配多个时,都匹配最长)。常用的正则模式有(其中,"扩展"在egrep命令中可使用):
字符 |
模式含义 |
. |
匹配任何单个字符 |
* |
匹配零次或多次 |
? |
匹配零次或一次(扩展) |
+ |
匹配一次或多次(扩展) |
\{N\} |
匹配N次(扩展用{N} ) |
\{N,\} |
匹配N次或更多次(扩展用{N,} ) |
\{N,M\} |
匹配至少N次之多M次(扩展用{N,M} ) |
a|b|c |
匹配a或b或c(扩展) |
( ) |
分组符号,如r(able|dress) 匹配rable或rdress(扩展) |
[x-y] |
范围,如[0-9]匹配数字 |
[ ] |
匹配一组字符中的一个,如[abc] 匹配a或b或c |
^ |
匹配行首 |
$ |
匹配行尾 |
[^] |
匹配取反,[^0-9] 表示非数字,[^abc] 表示非a,b,c |
\b |
单词定界符,包含词首和词尾定界符 |
\< |
词首定界符 |
\> |
词尾定界符 |
\w |
等价 [A-Za-z0-9] |
\W |
\w 取反,等价 [^A-Za-z0-9] |
- POSIX字符类。指定匹配字符范围的另一种方法。grep 中和egrep中使用方法一样。tr命令后也可使用单中括号
tr [:punct:] X
,但为了保持一致,建议都用双中括号。作用:有些字符类比正则表达简单,有些命令只支持POSIX不支持正则(如tr)。
字符类 |
含义 |
[[:alnum:]] |
字符和数字,等同于[A-Za-z0-9] |
[[:punct:]] |
标点符号 |
[[:cntrl:]] |
控制字符 |
[[:space:]] |
空白字符,空格、制表符、竖向制表符、换行、回车等 |
[[:alpha:]] |
字母,等同[A-Za-z] |
[[:blank:]] |
空格户制表符Tab |
[[:digit:]] |
所有数字,等同[0-9] |
[[:lower:]] |
所有小写字母,等同[a-z] |
[[:upper:]] |
所有大写字母,等同[A-Z] |
[[:xdigit:]] |
所有 16 进位制的数字 |
grep [[:punct:]] t.txt
八、进程与作业
- 挂起进程
,比如进入vi后,在命令模式下,按
挂起进程。jobs查看作业状态。
> vi test.txt
[1]+ Stopped vi test.txt
> jobs
[1]+ Stopped vi test.txt
- 熟悉命令 ps,jobs, fg, bg, kill, trap, suspend。
九、其他话题
- 目录栈。通常,目录只保留了OLDPWD和PWD,无法再追踪更早之前的记录。pushd、popd,dirs提供了这方面的能力。使用pushd命令记录的目录栈保存在内置变量DIRSTACK中。
pushd cd到目录,并将目录放入目录栈
+/-N 目录栈的“环滑动”,+N从栈顶的第N个项目滑为栈顶,-N从栈底的第N个目录滑为栈顶
>pwd
/usr
> dirs -c
> pushd /usr/bin
/usr/bin /usr --永远把当前目录放在栈底
> pushd /var/lib
/var/lib /usr/bin /usr
> declare -p DIRSTACK
declare -a DIRSTACK='([0]="/var/lib" [1]="/usr/bin" [2]="/usr")'
dirs
-p 每行显示目录栈内一个目录
-v p的基础上带编号,编号大的在栈底
+/-N 不改变目录栈的内容,+N表示从栈顶数第N个目录,-N表示从栈底数第N个目录
> dirs
/var/lib /usr/bin /usr
> dirs -v
0 /var/lib
1 /usr/bin
2 /usr
> dirs -p
/var/lib
/usr/bin
/usr
> dirs +1
/usr/bin
popd 移除栈顶目录,并cd到新栈顶目录
+/-N
> popd
/usr/bin /usr
> pwd
/usr/bin
> dirs -v
0 /usr/bin
1 /usr
> dirs -v
0 /opt
1 /usr/lib
2 /usr/bin --从栈顶第2位
3 /usr
> pushd +2
/usr/bin /usr /opt /usr/lib
> dirs -v
0 /usr/bin --原从栈顶第2位
1 /usr
2 /opt --原栈顶滑下来了
3 /usr/lib
- 波浪号扩展
命令 |
含义 |
cd ~ |
等价cd $HOME |
cd ~username |
到username的home |
~+ |
等价PWD,cd ~+ |
~- |
等价OLDPWD,cd ~- |
~+N 或~N |
等价dirs +N |
~-N |
等价dirs -N |
- 交互式shell和非交互式shell、登录shell和非登录shell
- expand_aliases别名功能,交互式shell默认打开,非交互式shell默认关闭。
I. 不要在脚本内使用别名,不然要显示打开expand_aliases。
II. 别名不能export,要想在其他shell中使用,将别名放到脚本里,再source 脚本名。
III. 函数内定义的别名,在函数调用前不能使用。
IV. 综上,不要在脚本和函数内使用别名。
- 并行处理。常用名命令grep,wc,awk和sed等都是单线程的,只能使用一个cpu。服务器有多个cpu时,可以利用GNU的parallel命令,把负载分配到各个cpu上。
I. parrallel命令通常需要下载安装,官网。
II. 使用说明。
mpstat
> cat /proc/cpuinfo |grep processor | wc -l
2
十、Bash调试
- 正式运行脚本前,先
set -n
或set -o noexce
,读命令,解释但不执行,用来检查脚本语法
set -u
或 set -o nounset
,有变量未定义时提示错误信息
shopt -s shift_verbose
,shift移动数大于参数个数时,提示错误
set -v
或 set -o verbose
,为调试打开verbose模式,脚本运行时显示读入的每行命令,并原样打印
set -x
或 set -o xtrace
,为调试打开echo模式,脚本运行时显示变量替换后的每行命令和参数。只调试一段代码时,在代码前后添加:
set -xv
待调试代码段
set +xv
set -x
的提示符为PS4,默认为+
,可修改成PS4=+'$[LINENO]'
打印代码行号。
bash -x 脚本
和 set -x
是一样的,相当于在脚本前增加了set -x
。便于脚本测试。
- 利用trap捕获信号来调试
- 打印内容调试
- 利用BASH_SOURCE,BASH_SUBSHELL,FUNNAME等内置变量。
- 输出重定向获取log文件来分析
- 在代码中增加“调试块”,if语句开控制变量调试信息是否打印
if [$DEBUG = YES];then
打印调试信息
fi
DIR_T="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 在tmp文件夹下创建脚本test.sh 和a.sh
> cat test.sh
echo BASH_SOURCE=${BASH_SOURCE[0]}
echo \$0=$0
DIR_T="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo ${DIR_T}
> cat a.sh
echo "This is a.sh"
source test.sh
> ./test.sh
BASH_SOURCE=./test.sh --相对路径
$0=./test.sh --相对路径
/opt/user/tmp --绝对路径
> bash test.sh
BASH_SOURCE=test.sh
$0=test.sh
/opt/user/tmp
> ../tmp/test.sh
BASH_SOURCE=../tmp/test.sh --相对路径
$0=../tmp/test.sh --相对路径
/opt/user/tmp
> bash ../tmp/test.sh
BASH_SOURCE=../tmp/test.sh
$0=../tmp/test.sh
/opt/user/tmp
> ./a.sh
This is a.sh
BASH_SOURCE=test.sh
$0=./a.sh --父脚本的名字
/opt/user/tmp
> echo $BASH_SUBSHELL
0
> (echo $BASH_SUBSHELL)
1
> ( ( ( ( (echo $BASH_SUBSHELL) ) ) ) )
5
> bash
> echo $SHLVL
1
> echo $BASH_SUBSHELL
0