【系统性学习】Linux Shell易忘重点整理

本文主要基于《实用Linux Shell编程》总结,并加入一些网上查询资料和博主自己的推断。
其中命令相关的,已抽取出来在另一篇系统性学习】Linux Shell常用命令中,可以一起使用。

文章目录

    • 一、基础知识
    • 二、命令与环境
    • 三、变量和数组
    • 四、条件流程控制
    • 五、循环
    • 六、函数
    • 七、通配符、正则表达和文本处理
    • 八、进程与作业
    • 九、其他话题
    • 十、Bash调试

一、基础知识

  1. Linux 系统主要目录及简单描述
    在这里插入图片描述
    【系统性学习】Linux Shell易忘重点整理_第1张图片
目录 描述
/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(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
  1. 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
  • 第五列表示文档容量大小
  • 第六列表示文档最后修改时间,注意不是文档的创建时间
  • 第七列表示文档名称。以点(.)开头的是隐藏文档
  1. 账户权限说明:对于文件,读:可以查看 写:可以修改 执行:可以运行。对于目录,读:可以浏览,用ls 写:可以创建和删除文件 执行:可以进入该目录。
  2. SUID:有的可执行二进制文件的账户权限为rws,s是SUID(即set uid),表示其他账户在执行时,有文件所有者的权限。如passwd命令会改/etc/shadow文件,这个文件只有root用户可以修改。但其他账户同样可以执行passwd修改该文件。原因就是passwd文件是s权限。chmod u+s filechmod 4755 file添加该权限。rwS显示S是不正常的,给文件添加x权限才会正常。SUID仅对可执行二进制文件起作用。
  3. SGID:有的可执行二进制文件的所属组权限为rws,,s是SGID(即set gid)表示其他账户在执行时,具有文件所属组的权限。对目录设置SGID,则作用为:任何账户如果可以在该目录内建立新文件或新的子目录,那么新文件和子目录的所属组与该目录的所属组保持一致。SGID属性要保证目录或文件对所属组由x权限,否则为大写S。chmod g+s filechmod 2755 file添加该权限
  4. SBIT:粘滞位,t是SBIT只有目录可以设置,作用是:在一个大家都有权限的目录下,账户不能删除别人的文件或目录。chmod o+t filechmod 1755 file添加该权限。SBIT要求其他账户有执行权限,否则为大写T。

二、命令与环境

  1. linux命令分为内置命令和外部命令:help查看所有内置命令和关键字列表, help command查看内置命令详情。外置命令:which command查看外置命令位置,which无法对内置命令生效。command --help显示外置命令详情。
  2. 内置变量PS1,主命令提示参数。重新登陆shell则恢复。永久保持写入~/.bashrc中。
\h 计算机名
\t 当前时间
\u 账户名
\w 当前路径
> PS1="\h@\u@\t$" #输入
host-10-31-17-80@user_client@16:17:03$ #提示符从>改变了
  1. 搜索路径PATH,路径用:隔开,按顺序先后查找,找到就停止。在原路径后面加路径PATH='$PATH:/tmp/bin'
  2. 要永久改变环境变量,将修改写进~/.bashrc文件中。
  3. 权限掩码umask
  4. source和点命令:souce file. file等价,如果file不在当前目录下,会在$PATH下找。
  5. 直接执行脚本文件和source都能执行脚本中的命令。差异:a. 脚本在子shell中运行,而source是在父shell b. 直接运行脚本需要脚本有x权限,source不需要
  6. 命令解释顺序及改变。命令类型查看用type command
alias->keywords->function->built-in->$PATH
别名->关键字->函数->内置命令->外部命令
  • command <命令> 禁用别名和函数,先处理内置命令和外部命令
  • builtin <命令> 只查找内置命令
  • enable 禁用(-n)和使能内置命令(尽量少用,要避免自己的编写的命令和内置命令重名)
  1. 命令或程序结束后会返回一个退出状态,取值0-255,0表示成功执行。内置变量$?存了上一条命令的退出状态 。
  2. 内置命令true用于返回成功的状态,false则是失败。
  3. 管道左侧|右侧,理解成左侧命令的输出,会给到右侧命令的输入,所以右侧命令不需要写代表输入的参数。
  4. 执行一个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   +------+
      +--------------------------+                
  1. 输入重定向: 命令 输入->命令 < 文件名(不能是带输出的命令)
  2. 输出重定向: 命令(能产生输出)->命令(能产生输出) >文件名命令(能产生输出) >>文件名追加模式。> 文件名可以创建空文件或者清空文件。输出重定向是不安全的。如果想不覆盖源文件,可开启noclobber选项,set -o noclobber
  3. 标准错误输出(stderr),对应fd/2,普通重定向>不起作用。需要 2> 实现重定向。
  4. 同时处理,1>file1 2>file2标准到file1,错误到file2,>file 2>&1同时输出到file(等价 &> file>& file)。
  5. “黑洞”,/dev/null一般用作垃圾箱,把不要的输出重定向到这里,如2> /dev/null
  6. 一行多命令,用;隔开。
  7. 后台执行:command &,执行后会输出 [工作号] 进程号
  8. 续行:命令太长可用\来分行,PS2控制续行提示符。
  9. 特殊文件名处理:空格前面加\,-a.txt这样-开头的,用vi -- -a.txtvi ./-a.txt。因为-后面会被认为是选项。Bash规定--(空格)后面的东西不是选项,而是文件名或参数。
  10. 小括号,大括号,中括号

三、变量和数组

  1. Bash只有字符串类型。整数型字符串赋值被变量时,变量相当于多了个整数属性,可以进行整数运算。
  2. 变量定义时,等号左右两边不能有空格。引用变量时,前面加$
  3. 双引号作用,保留空格。a= b=" ",a是为空的,b才能保留空格。
  • 单引号:相当于raw字符串:被单引号括起的内容不管是常量还是变量者不会发生替换。
  • 双引号:把双引号内的内容输出出来;如果内容中有命令、变量等,会先把变量、命令解析出结果,发生替换,然后在输出最终内容来。
  • 不加引号:不会将含有空格的字符串视为一个整体输出, 如果内容中有命令、变量等,会先把变量、命令解析出结果,然后在输出最终内容来,如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以用。
  1. 取命令执行结果:`命令`,$(命令)都可以取命令输出的结果。要保留换行符、首尾空格等符号,则用"$(命令)"。可以把()当做子shell,里面的操作不会对父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)
  1. 整型计算:放在双括号间或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
  1. 浮点运算:用bc或者awk。echo "scale=5;表达式|bc"或者直接bc开客户端运算,quit退出。
  2. 其他进制:bash支持其他进制运算,只要在算数表达式能生效的地方都可以使用。两种书写方法,a. 基数#数值,基数可取2到64。b. 0开头表示8进制,0x开头表示十六进制:
((w=8#16)) #8进制的16赋值给w
((w=016))  #等价
((w=16#25)) #16进制的25赋值给w
((w=0x25))  #等价
  1. 数组: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
  1. 关联数组(其他语言里的map):Bash4以上版本才支持。使用和数组类似,定义如下:
declare -A age=([Mike]=12 [Jack]=24 [Tom]=30)
# 显示详情
declare -p age

# 取建
echo ${!age[@]}
# 取值
echo ${age[@]}
  1. Bash中的特殊变量。P118
变量 含义
$0 脚本自身的名字,或者shell的名字
$N 脚本或函数的位置参数,$1,$2,...${10},注意大于9要用{}包起来
$# 位置参数个数,不包括$0
$* 所有位置参数(整体作为一个字符串),不包括$0
$@ 所有位置参数(每个作为独立字符串),不包括$0
$? 上一条命令退出状态
$$ 当前shell进程ID
$! 最后一个命令的进程ID
$- 当前shell的选项,若echo $- 的输出中包含i,则表示是交互式shell,包含C则表示noclobber开启
$_ 上条命令的最后一个参数
  1. set --清除所有位置参数,不包括$0
  2. 父shell与子shell及其进程ID。
  3. 一些常用内置变量(或者说环境变量)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/用户名,不要改,会影响cdcd ~的结果
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
  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开头的变量
  1. 常用内置字符串操作(支持通配符)。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"

四、条件流程控制

  1. 条件判断命令:test 条件表达式[ 条件表达式 ],中括号前后一定要有空格。$?取命令退出结果来看条件是否满足。满足则退出结果为0,否则为1。
  2. 整型关系运算:
条件表达式 含义
[ 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
  1. 字符串关系运算,注意需要加\转义,且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用引号括起来,则当做普通字符串。
  1. 文件属性判断,
条件表达式 含义
[ -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. 逻辑与:[ 条件表达式1 -a 条件表达式2 ],逻辑或:[ 条件表达式1 -o 条件表达式2 ],逻辑非:[ ! 条件表达式 ]。test同样。优先级:非、与、或,多条件可用(),括号最优先。
  2. 双中括号代替test或[].[[]]使得P146:
  • a. (,<等不用加反斜杠;
  • b. 保留字符串首尾有空格时,也不需要加双引号
  • c. 判断字符串相等或者不等时,右侧支持通配模式。*代表0或多个字符,?代表一个字符
  • d. 支持&&|| 表达 代替 -a ,-o
  1. 双小括号助力整数条件运算。(())使用场景:
    I. 计算表达式,((表达式))等价于 let 表达式 。表达式结果不是逻辑0或1的时候,该表达式不报错的退出状态永远是0。
    II. 上述表达式可以是整数相关的条件表达式,使得
    a. (,<等不用加反斜杠;
    b. >,<,!=可应用于整数,并且>=,<=,==也可使用
    c. 支持&&|| 表达 代替 -a ,-o

  2. 命令的与或非:
    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
  1. 判断变量是否有定义:[-v 变量名]test -v 变量名。结果为0则表示有定义。或者set,从结果中找,存在则表示有定义,但这种方式太繁琐。
  2. if控制结构,可嵌套使用。
# if命令
if 命令 #该命令的退出结果为0,则执行then后面的语句
then
	命令1
	命令2
	...
if

if 命令;then 命令1;命令2...;fi  # 单行模式

#if-else 命令
if 命令
then 
	命令(命令组)
else #可以没有
	命令(命令组)
fi

if 命令;then 命令(命令组);else 命令(命令组);fi

#if-elif 命令
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-else的替换
if cmd1; then cmd2;else cmd3;fi
cmd1 && cmd2 ||cmd3 # 有可能等价
  1. 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
  1. exit的退出状态默认为前一条命令的退出状态。也可以exit N带整型参数,则退出状态为N,这个特点可以配合if(产生exit 不同结果)和case(对不同结果做处理)命令使用。
  2. here文档和case可模拟select命令的功能。详见常用命令here。
# 结合case使用,模拟select命令
cat << INPUT
choose your role
A)student
B) teacher
INPUT

read var
case $var in
	A)
		...;;
	B) 
	 	...;;
	*)
		echo "error";;
esac

五、循环

  1. 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


# IFS注意点
> OLD_IFS="$IFS"
> IFS="$OLD_IFS" #用完后要恢复



# 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 常用大括号扩展
for i in {1..10} # 或{a..z}, a{b,c}k,{10..1}等
do 
	...
done
  1. 算数for循环,三个表达式不是必须的,但;需要保留。循环条件为空时,表示无限循环。
for ((变量初始化;循环条件;变量值更新))
do
	命令(命令组)
done

# 样例1
for ((i=0;i<10;i++));do echo $i;done
# 样例2
i=0
for ((;i<10;));do echo $i;((i++));done
  1. while循环。先判断while后面命令的退出状态,为0时,do后面的命令再执行。
while 命令
do
	命令(命令组)
done
  1. until循环。先判断while后面命令的退出状态,为0时,do后面的命令再停止。
until 命令 #等价于 while !命令
do
	命令(命令组)
done
  1. break和continue可以跳出循环,包括for、while、until。break Ncontinue N跳出N层循环。技巧if 命令;then break/continue;fi可写成命令 && break/continue
  2. 循环的重定向以及和管道的配合。循环结构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 

六、函数

  1. 函数定义有三种形式,可接受位置参数,调用时用法和脚本一样
function 函数名
{
	命令(命令组)
}

函数名()
{
	命令(命令组)
}

function 函数名()
{
	命令(命令组)
}
# 一行形式,右大括号前必须要有一个分号
function 函数名 {命令(命令组);}

# 使用
函数名 arg1 arg2 arg3 #函数体力如果有echo,就打印echo内容

# 调用
source 函数定义脚本
$(函数名)
$(函数名 arg1 agr2)
  1. 函数内变量,如果和全局变量同名,则为全局变量,否则需要加上 local 或者declare声明成局部变量;如果是非同名变量,则为局部变量,加上declare -g 则可声明成全局变量。
  2. 当前函数名,存在内部变量FUNNAME中。FUNNAME是一个数组,第一个元素为当前函数名,第二个元素为上一层调用函数名,declare - p FUNNAMEecho ${FUNAME[*]}查看调用链。
  3. 函数的导出与清除。declare -fx fun1, 导出函数,等同 export -f fun1。清除 unset -f 函数名unset 函数名declare -f fun1查看函数定义。
  4. 函数返回命令return, 可带参数N。执行return后,函数中后续命令不再执行。return不带参数时,函数的返回状态为return上一条命令的状态。retrun N时,,函数的返回状态为N。可当做默认函数结尾有个隐含的return。
  5. 递归函数,可以实现,但运行效率较低,且占用系统资源较多,不提倡写递归函数。

七、通配符、正则表达和文本处理

  1. 通配符模式,glob(也叫wildcard)。支持以下几种
通配符 含义
* 表示任意字符
? 表示一个字符
[] 范围替换[abc]表示a或b或c,[0-9],[a-z]前面的字符不能大于后面的字符。[A-Z0-9]组合在一起也可以。
[!] 不在范围内,[!abc]表示不为a或b或c,[!0-9]表示不为0-9
  1. 扩展通配模式,可通过shopt -s extglob打开扩展(-u 表示关闭),支持:
通配模式 含义
*(pattern) 匹配所给模式零次或多次,如*(m),表示0个或多个m
?(pattern) 匹配所给模式零次或一次
+(pattern) 匹配所给模式一次或多次
@(pattern) 匹配所给模式仅一次
!(pattern) 不匹配所给模式
  1. 通配符模式使用场景
    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  --双引号,通配符失效
  1. 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
  1. 正则表达式,各语言的正则表达式支持范围略有不同,具体可查阅【中文,英文】。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]
  1. 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 #过滤出包含空白字符的所有行

八、进程与作业

  1. 挂起进程,比如进入vi后,在命令模式下,按挂起进程。jobs查看作业状态。
> vi test.txt # 按
[1]+  Stopped                 vi test.txt
> jobs
[1]+  Stopped                 vi test.txt
  1. 熟悉命令 ps,jobs, fg, bg, kill, trap, suspend。

九、其他话题

  1. 目录栈。通常,目录只保留了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
  1. 波浪号扩展
命令 含义
cd ~ 等价cd $HOME
cd ~username 到username的home
~+ 等价PWD,cd ~+
~- 等价OLDPWD,cd ~-
~+N~N 等价dirs +N
~-N 等价dirs -N
  1. 交互式shell和非交互式shell、登录shell和非登录shell
  2. expand_aliases别名功能,交互式shell默认打开,非交互式shell默认关闭。
    I. 不要在脚本内使用别名,不然要显示打开expand_aliases。
    II. 别名不能export,要想在其他shell中使用,将别名放到脚本里,再source 脚本名。
    III. 函数内定义的别名,在函数调用前不能使用。
    IV. 综上,不要在脚本和函数内使用别名。
  3. 并行处理。常用名命令grep,wc,awk和sed等都是单线程的,只能使用一个cpu。服务器有多个cpu时,可以利用GNU的parallel命令,把负载分配到各个cpu上。
    I. parrallel命令通常需要下载安装,官网。
    II. 使用说明。
mpstat # 查看cpu信息,该命令不是所有bash都有
> cat /proc/cpuinfo |grep processor | wc -l # 获取cpu数
2

十、Bash调试

  1. 正式运行脚本前,先set -n set -o noexce,读命令,解释但不执行,用来检查脚本语法
  2. set -uset -o nounset,有变量未定义时提示错误信息
  3. shopt -s shift_verbose,shift移动数大于参数个数时,提示错误
  4. set -vset -o verbose,为调试打开verbose模式,脚本运行时显示读入的每行命令,并原样打印
  5. set -xset -o xtrace,为调试打开echo模式,脚本运行时显示变量替换后的每行命令和参数。只调试一段代码时,在代码前后添加:
set -xv
待调试代码段
set +xv
  1. set -x的提示符为PS4,默认为+,可修改成PS4=+'$[LINENO]'打印代码行号。
  2. bash -x 脚本set -x是一样的,相当于在脚本前增加了set -x。便于脚本测试。
  3. 利用trap捕获信号来调试
  4. 打印内容调试
  5. 利用BASH_SOURCE,BASH_SUBSHELL,FUNNAME等内置变量。
  6. 输出重定向获取log文件来分析
  7. 在代码中增加“调试块”,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

# BASH_SUBSHELL
> echo $BASH_SUBSHELL
0
> (echo $BASH_SUBSHELL)
1
> ( ( ( ( (echo $BASH_SUBSHELL) ) ) ) )
5
> bash
> echo $SHLVL
1
> echo $BASH_SUBSHELL
0

你可能感兴趣的:(Linux,linux,学习,服务器)