BASH流程

bash开头

#!/bin/sh
#!/bin/bash
#!/usr/bin/awk
#!/bin/sed
#!/usr/bin/tcl
#!/usr/bin/expect #<===expect解决交互式的语言标准开头
#!/usr/bin/perl	  #<===perl语言解释器
#!/usr/bin/env python #<===python语言解释器
  • /bin/sh 往往是指向/bin/bash的符号链接

bash执行流程

​ shell脚本是从上而下,从左往右依次执行每一行的命令以及语句的,即执行完了一个命令后再执行下一个,如果在shell脚本中遇到子shell时,就会先执行子shell内容,完成后再返回父脚本内容及后续的命令与语句

只有子shell能继承父shell,而父shell不能继承子shell

​ 通常情况下,在执行shell脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子shell脚本

bash script-name 或 sh script-name

1. 这是当脚本文件本身没有可执行权限时,通常使用的方法
2. 执行的时候,会生成`子shell`
3. 不要有可执行
4. `子shell继承父shell的变量,但子shell不能使用父shell的变量,除非使用export`
cat test.sh
user=`whoami`sh test
echo $user #输出什么# 因为脚本是在子shell中定义了变量,而当子shell执行完后,变量就没了,所以父shell输出的时候为空

path/script-name 或 ./script-name

1. 指在当前路径下执行脚本(脚本需要有执行权限),需要先将脚本文件改为可执行,产生子shell

source script-name 或 . script-name

1. 执行的时候,实在当前shell执行,不会生成子shell,所有shell共享环境变量
2. 不要有可执行
3. #子shell中的信息会被共享到父shell
4. 不创建subshell,在当前shell环境下读取并执行script-name中的命令,相当于顺序执行script-name里面的命令

sh<script-name 或 cat scripts-name|sh


/bin/bash -c

# 扩展#!/bin/sh是#!/bin/bash的缩减版# 作用:让 bash 将一个字符串作为完整的命令来执行# 问题:执行命令"sudo echo "kettle" >> nohup.log"报错-bash: nuhup.log: Permission denied# 原因:命令中含有echo 和>>两条bash命令,而sudo只会给后面的第一个命令赋予root权限,而">>"没有权限# 解决:使用/bin/bash -c指定将命令转为一个完整命令执行sudo /bin/bash -c 'echo "kettle" >> nohup.log'# 默认	/bin/bash ls    # ls被默认为脚本/bin/bash -c ls # ls被默认为命令

$ (commond) &commond

它的作用是让命令在子shell中执行

exec commond

# 作用替换当前的shell却没有创建一个新的进程。进程的pid保持不变shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行当在一个shell里面执行exec ls后,会列出了当前目录,然后这个shell就自己退出了。(后续命令不再执行),因为这个shell已被替换为仅执行ls命令的进程,执行结束自然也就退出了需要的时候可以用sub shell 避免这个影响,一般将exec命令放到一个shell脚本里面,用主脚本调用这个脚本,调用点处可以用bash a.sh(a.sh就是存放该命令的脚本),这样会为a.sh建立一个sub shell去执行,当执行到exec后,该子脚本进程就被替换成了相应的exec的命令

父/子SHELL

获取当前shell的进程号

1: echo $BASHPID
2: echo $$

开启或关闭子shell

# 开启bash─sshd(1076)───sshd(1573)───bash(1577)───bash(1650)───pstree(1665)
# 关闭exit

如何产生子shell

​ Linux上创建子进程的方式有三种:一种是fork出来的进程,一种是exec出来的进程,一种是clone出来的进程(此处无需关心clone,因为它用来实现Linux中的线程)

  • fork是复制进程,它会复制当前进程的副本(不考虑写时复制的模式),以适当的方式将这些资源交给子进程。所以子进程掌握的资源和父进程是一样的,包括内存中的内容,所以也包括环境变量和变量。但父子进程是完全独立的,它们是一个程序的两个实例
  • exec是加载另一个应用程序,替代当前运行的进程,也就是说在不创建新进程的情况下加载一个新程序。exec还有一个动作:在进程执行完毕后,退出exec所在的shell环境
  • 为了保证进程安全,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上调用exec来加载新程序替代该子进程

​ 除了上面可以通过bash命令手动开启一个子shell;例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载cp程序覆盖子bash进程变成cp进程

不进入子shell的情况

执行bash内置命令时
# let是内置命令,不进入子shell
echo $BASHPID let a=$BASHPIDecho $a
执行shell函数时

​ 其实shell函数就是命令,它和bash内置命令的情况一样,直接执行时不会进入子shell

# 定义一个函数,输出BASHPID变量的值
fun_test (){ echo $BASHPID; } 
echo $BASHPID
fun_test # 结果
[root@boy ~]# fun_test (){ echo $BASHPID; } 
[root@boy ~]# echo $BASHPID
1650
[root@boy ~]# fun_test 
1650
执行非bash内置命令时

​ 例如执行cp命令、grep命令等,它们直接fork一份bash进程,然后使用exec加载程序替代该子bash。此类子进程会继承所有父bash的环境。但严格地说,这已经不是子shell,因为exec加载的程序已经把子bash进程替换掉了,这意味着丢失了很多bash环境。在bash文档中,直接称呼这种环境为"单独的环境",和子shell的概念类似


进入子shell的情况

将命令放在管道后

​ 如果将命令放在管道后,则此命令将和管道左边的进程同属于一个进程组,所以仍然会创建子shell。这时候的子shell的作用是为bash内置命令提供执行环境

echo "https://blog.csdn.net/qq_43808700?spm=1000.2115.3001.5343" | readecho $REPLYecho是内置命令、read也是内置命令使用 read 读取数据时,如果没有提供变量名,那么读取到的数据将存放到环境变量REPLY中使用管道操作符“|”可以把一个命令的标准输出传送到另一个命令的标准输入中,连续的|意味着第一个命令的输出为第二个命令的输入,第二个命令的输出为第三个命令的输入,依次类推

read内置命令位于管道后,read会在子shell中执行

echo命令在父Shell中执行,echo通过管道将内容输出到子进程中,管道可以用于父子进程之间通信,因此子shell可以拿到父shell输出的内容,因此子shell中read一定将拿到的内容保存在了环境变量REPLY中而子进程的环境变量对父进程是不可见的,所以读取失败

[root@boy ~]# echo 123 | { read; echo $REPLY; }
123
[root@boy ~]# echo 123 | read
[root@boy ~]# echo $REPLY
``

只要放在管道符之后的命令,则此命令将和管道左边的进程同属于一个进程组 2,就会创建子shell,管道符右边的命令就会在子shell中运行

执行bash命令本身时

​ bash命令本身是bash内置命令,在当前shell环境下执行内置命令本不会创建子shell,也就是说不会有独立的bash进程出现,而实际结果则表现为新的bash是一个子进程。其中一个原因是执行bash命令会加载各种环境配置项,为了父bash的环境得到保护而不被覆盖,所以应该让其以子shell的方式存在虽然fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量

执行shell脚本时

执行shell函数时

其实shell函数放在管道后会进入子shell

fun_test (){ echo $BASHPID; } 
echo $BASHPIDcd | fun_test
命令替换

​ 当命令行中包含了命令替换部分时,将开启一个子shell先执行这部分内容,再将执行结果返回给当前命令。因为这次的子shell不是通过bash命令进入的子shell,所以它会继承父shell的所有变量内容。这也就解释了"echo $(echo &&)“中”)"中"echo &&"的结果是当前bash的pid号,而不是子shell的pid号,但"echo $(echo $BASHPID)"却和父bash进程的pid不同,因为它不是使用bash命令进入的子shell

echo $BASHPIDecho $ $$echo $(echo $BASHPID) 
使用括号()组合一系列命令
(echo $BASHPID)
放入后台运行的任务

它不仅是一个独立的子进程,还是在子shell环境中运行的。例如"echo hahha &

echo $BASHPIDecho $BASHPID &  
进程替换

既然是新进程了,当然进入子shell执行。例如"cat <(echo haha)"

echo $BASHPIDcat <(echo $BASHPID) cat <(echo $BASHPID) :进程替换"<()"进入子shell

变量

变量简介

  • 常规变量的字符串定义变量值应该加双引号,并且等号前后不能有空格,需要加强引用的(即所见即所得的字符引用),则用单引号(‘’),如果是命令的引用则使用(``,$())

  • 变量可分为两类:环境变量(全局变量)和普通变量(局部变量)

    • 环境变量也称为全局变量,可以创建他们的shell及其派生出来的任意子进程shell中使用,环境变量又分为:自定义环境变量和bash内置的环境变量
    • 普通变量也可称为局部变量,只能在创建他们的shell函数或者shell脚本中使用

环境变量

  • 环境变量一般是指用export内置命令导出的变量,用于定义Shell的运行环境,保证Shell命令的正确执行。Shell 通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、Shell脚本和各类应用
  • 按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用export 命令导出定义,例如:正确的环境变量定义方法为export OLDGIRL=1。
$USER 当前用户名
$HOSTNAME 显示当前主机名
$UID 当前用户的id
$PWD 当前目录
$PATH 命令搜索路径
$IFS shell的分隔符
$HOME 用户主目录路径名
$TERM 终端类型
echo $LOGNAME 登录名

普通变量

引用实例

# 脚本内容a=192.168.0.1.2b='192.168.1.2'c="192.168.1.2"echo "a=$a"echo "b=$b"echo "c=${c}"# 脚本输出a=192.168.0.1.2a=192.168.0.1.2a=192.168.0.1.2
# 脚本内容a=192.168.1.2-$ab='192.168.1.2-$a' #如果有命令和变量,需要反引起来c="192.168.1.2-$a"echo "a=$a"echo "b=$b"echo "c=${c}"
# 脚本输出a=192.168.1.2-192.168.1.2b=192.168.1.2-$ac=192.168.1.2-192.168.1.2-192.168.1.2经验:数字内容的变量定义可以不加引号,其他没有特别要求的字符串等定义最好都加上双引号,如果真的需要原样输出就加单引号,定义变量加双引号是最常见的使用场景。

引号区别

# 单引号所见即所得,即输出时会将单引号内的所有内容都原样输出,或者描述为单引号里面看到的是什么就会输出什么,这称为强引用# 双引号(默认)输出双引号内的所有内容;如果内容中有命令(要反引下)、变量、特殊转义符等,会先把变量、命令、转义字符解析出结果,然后再输出最终内容,推荐使用,这称为弱引用# 无引号赋值时,如果变量内容中有空格,则会造成赋值不完整。而在输出内容时,会将含有空格的字符串视为一个整体来输出;如果`内容中有命令(要反引下$(),``)`、变量等,则会先把变量、命令解析出结果,然后输出最终内容;如果字符串中带有空格等特殊字符,则有可能无法完整地输出,因此需要改加双引号。-般连续的字符串、数字、路径等可以不加任何引号进行赋值和输出,不过最好是用双引号替代无引号的情况,特别是对变量赋值时# 反引号“一般用于引用命令,执行的时候命令会被执行,相当于$(),赋值和输出都要用“将命令引起来
echo 'today is `date`' #能被解析命令
echo 'today is $(date)' #这种不能被解析命令echo "today is `date`"
echo "today is $(date)"
awk 中变量引号问题:看老男孩shell

/etc/init.d/functions函数库可以学习

引号嵌套

# 反引号可以嵌套使用,但内层的单引号必须加上\ 进行转移
$ abc=`echo The number of users is \`who| wc -l\``
$ echo $abc
The number of users is 2

查看设置变量

在查看设置的变量时,有3个命令可以显示变量的值: set、 env 和declare (替代早期的typeset)set 命令输出所有的变量,包括全局变量和局部变量

env(printenv) 命令只显示全bash Shell的所有参数配置信息全局变量

declare 命令输出所有的变量、函数、整数和已经导出的变量

set -o命令显示bash Shell的所有参数配置信息,set也可以显示环境变量,包括局部变量

unset $VARIABLE 消除环境变量和局部变量

设置环境变量(全局变量)

①:export 变量名=value
②:变量名=value; export 变量名
③:declare -x 变量名=value
  • export

  • declare

  • set

  • 用户环境变量设置
cat ./bashrc   #推荐这里设置
cat ./bash_profile
  • 全局环境变量配置
cat /etc/profile
cat /etc/bashrc			#推荐在此文件中优先设置
cat /etc/profile.d

若要在登陆后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/下即可,不需要添加执行权限

设置登录提示

  • /etc/motd里增加提示的字符串
cat /etc/motd
welcome to xxx
  • 在/etc/profile.d/下增加输出脚本

变量加载流程

在登录Linux系统并启动-一个bash shell 时,默认情况下bash会在若千个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash检查的环境变量文件的情况取决于系统运行Shell的方式。系统运行Shell的方式- ~ 般有3种:

  1. 通过系统用户登录后默认运行的shell
  2. 非登录交互式运行shell
  3. 执行脚本运行交互式shell

​ 用户登录系统后首先会加载/etc/profile全局变量文件,这是linux系统上默认的shell主环境变量文件,系统上每个用户登录都会加载这个文件

​ 当加载完/etc/profile文件后,才会执行/etc/profile.d/目录下的脚本文件这个目录下的脚本有很多,例如:系统的字符集设置/etc/sysconfig/i18n等,在,我们也把脚本的起始加载放到这个目录下,以便用户登录后马上运行脚本

​ 之后开始运行HOME/.bashprofile(用户环境变量文件),在这个文件中又会去找HOME/.bash_profile(用户环境变量文件),在这个文件中又会去找HOME/.bashprofile()HOME/.bashrc(用户环境变量文件),如果有,则执行,如果没有则不执行,在$HOME/.bashrc文件中又会去找/etc/bashrc(全局环境变量文件),如果有,则执行,如果没有,则不执行。

如果用户的shell不是登陆时启动的(比如手动敲下bash时启动或者其他不需要输入密码的登陆以及远程SSH连接情况),那么这种非登录shell只会加载HOME/.bashrc(用户环境变量文件),并去找/etc/bashrc(全局环境变量文件)。因此如果希望在非登录shell下也可以读到环境变量等内容,需要将变量设定写入HOME/.bashrc(用户环境变量文件),并去找/etc/bashrc(全局环境变量文件)。因此如果希望在非登录shell下也可以读到环境变量等内容,需要将变量设定写入HOME/.bashrc()/etc/bashrc()shellHOME/.bashrc或者/etc/bashrc,而不是$HOME/.bash_profile或者/etc/profile

特殊变量

$0 当前程序的名称,即文件名
$n (n=1……9) 第n个参数比如$1 、2。n>9时:需要用大括号括起来2。n>9时:需要用大括号括起来2n>9{10}
$* 当前程序所有的参数,不包括程序名称和程序内写好的参数,通过一个字符串返回
$@ 输出所有的参数,与$*相同,只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)
$# 当前程序的参数个数
$? 最近一次执行的命令或shell脚本的出口状态,一般0为成功,其他是失败比如127
# $0 实例sh n.sh  #输出n.shsh /server/srcipt/n.sh #输出/server/scripts/n.sh 如果执行的脚本中带有路径,那么$0获取的值就是脚本的名字加路径# 获取路径或脚本名dirname /server/scripts/n.sh #/server/scripts 获取脚本路径basename /server/scripts/n.sh #n.sh 获取脚本名字# 取出路径或脚本名dirname $0basename $0

eval

# eval args

exec

# exec命令参数exec命令能够在不创建新的子进程的前提下,转去执行指定的命令,当指定的命令执行完毕后,该进程(也就是最初的shell)就终止了[root@xx]# exec date 执行完后就会退出这个shell# 当使用exec打开文件后,read命令每次都会将文件指针移动到文件的下一行读取,直到文件末尾,利用这个可以实现处理文件内容seq 5 >/tmp/tmp.log
cat exec.sh
exec </tmp/tmp.log #读取log内容
while read line    #利用read一行行读取处理
doecho $line
done
echo ok

read

# read [-rs] [-a ARRAY] [-d delim] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [var_name1 var_name2 ...]选项说明:-a:将分裂后的字段依次存储到指定的数组中,存储的起始位置从数组的index=0开始。-d:指定读取行的结束符号。默认结束符号为换行符。-n:限制读取N个字符就自动结束读取,如果没有读满N个字符就按下回车或遇到换行符,则也会结束读取。-N:严格要求读满N个字符才自动结束读取,即使中途按下了回车或遇到了换行符也不结束。其中换行符或回车算一个字符。-p:给出提示符。默认不支持"\n"换行,要换行需要特殊处理,见下文示例。例如,"-p 请输入密码:"-r:禁止反斜线的转义功能。这意味着"\"会变成文本的一部分。-s:静默模式。输入的内容不会回显在屏幕上。-t:给出超时时间,在达到超时时间时,read退出并返回错误。也就是说不会读取任何内容,即使已经输入了一部分。-u:从给定文件描述符(fd=N)中读取数据。

shift

在程序中使用一次shift语句,都会使所有的位置参数依次向左移动一位,并使位置参数$#减一,直到减为0

function a(){shift
}
a $@ #可以避免直接移动

seq

用法:seq [选项]... 尾数或:seq [选项]... 首数 尾数或:seq [选项]... 首数 增量 尾数
以指定增量从首数开始打印数字到尾数。-f, --format=格式     使用printf 样式的浮点格式-s, --separator=字符串        使用指定字符串分隔数字(默认使用:\n)-w, --equal-width    在列前添加0 使得宽度相同【自动补位】--help            显示此帮助信息并退出--version         显示版本信息并退出
# 指定分隔符  横着输出[root@localhost ~]# seq -s '#' 51#2#3#4#5# 以空格作为分格,且输出单数[root@localhost ~]# seq -s ' ' 101 2 3 4 5 6 7 8 9 10# 产生-2~10内的整数,增量为2[root@localhost ~]# seq -2 2 10-20246810# 一次性创建5个名为dir001 , dir002 .. dir005 的目录(%3g 表示宽度为3,不足用0补足)[root@cnblogs ~]# seq -f 'dir%03g' 1 5  dir001dir002dir003dir004dir005[root@cnblogs ~]# seq -f 'dir%03g' 1 5|xargs mkdir[root@cnblogs ~]# ls -l dir*# %3g 表示宽度为3,不足用0补足[root@cnblogs ~]# seq -f "%03g" 98 101098099100101
for i in `seq 1 254`
do	echo $i
done

exit

退出shell程序,选择一个数位作为返回状态

shell变量子串

表达式可以通配符

ID 表达式 说明
1 ${parameter} 返回变量内容
2 ${#parameter} 返回变量的长度
3 ${parameter:offset} 返回变量从offset开始取,不含offset
4 ${parameter:offset:length} offset开始取,取length长度
5 ${parameter#word}可以 从变量{parameter}开头开始删除最短匹配的word子串
6 ${parameter##word} 从变量{parameter}开头开始删除最长匹配的word子串
8 ${parameter%%word} 从变量{parameter}结尾开始删除最长匹配的word子串
9 ${parameter/pattern/string} 用string代替第一个匹配的pattern
10 ${parameter//pattern/string} a用string代替所有匹配的pattern
$ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:7}
7 8 9 0 a b c d e f g h
$ echo ${@:7:0}$ echo ${@:7:2}
7 8
$ echo ${@:7:-2}
bash: -2: substring expression < 0
$ echo ${@: -7:2}
b c
$ echo ${@:0}
./bash 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:0:2}
./bash 1
$ echo ${@: -7:0}# echo "${@:2}" ,它的意思就是输出入参的第2个至最后一个参数,且以空格分隔,合并为一个单词(word)

shell特殊变量扩展知识

  • 好像不止4个(自己查)

注意,每个运算符里面的冒号都是可选的

表达式 说明
${parameter:-word} 如果parameter变量值为空或未赋值,就会返回word字符串替代变量的值。 常用 用途:如果变量未定义,就返回备用的值,防止变量为空或未定义导致异常
${parameter:=word} 如果parameter变量值为空或未赋值,就会设置这个变量为word ,并返回其值。位置变量和特殊变量不适用
用途:基本同上一个,但是又额外给parameter赋值了
${parameter:?word} 如果parameter变量值为空或未赋值,word字符串将被作为标准错误输出,否则输出变量的值 用途:用于捕捉由于变量值为空或未定义而导致的错误,并退出程序
${parameter:+word} 如果parameter变量值为空或未赋值,则什么都不做,否则word字符串代替变量的值

local

作用:一般用于shell内局部变量的定义,多使用在函数内部

关于局部变量和全局变量:
(1)shell 脚本中定义的变量是global的,作用域从被定义的地方开始,一直到shell结束或者被显示删除的地方为止。
(2)shell函数定义的变量也是global的,其作用域从 函数被调用执行变量的地方 开始,到shell或结束或者显示删除为止。函数定义的变量可以是local的,其作用域局限于函数内部。但是函数的参数是local的
(3)如果局部变量和全局变量名字相同,那么在这个函数内部,会使用局部变量

字符串

判断读取字符串值

${var} 变量var的值, 与$var相同
${var-DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串
${var:+OTHER} 如果var被设置了, 那么其值就是$OTHER, 否则就为null字符串
${var?ERR_MSG} 如果var没被声明, 那么就打印$ERR_MSG *
${var:?ERR_MSG} 如果var没被设置, 那么就打印$ERR_MSG *
${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量
${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量
$ var1=11;var2=12;var3=  
$ echo ${!v@}             
var1 var2 var3  
$ echo ${!v*}  
var1 var2 var3  #${!varprefix*}与${!varprefix@}相似,可以通过变量名前缀字符,搜索已经定义的变量,无论是否为空值

字符串操作

${#string} $string的长度
${string:position} string中,从位置string中, 从位置string,position开始提取子串
${string:position:length} string中,从位置string中, 从位置string,position开始提取长度为$length的子串
${string#substring} 从变量string的开头,删除最短匹配string的开头, 删除最短匹配string,substring的子串
${string##substring} 从变量string的开头,删除最长匹配string的开头, 删除最长匹配string,substring的子串
${string%substring} 从变量string的结尾,删除最短匹配string的结尾, 删除最短匹配string,substring的子串
${string%%substring} 从变量string的结尾,删除最长匹配string的结尾, 删除最长匹配string,substring的子串
${string/substring/replacement} 使用replacement,来代替第一个匹配的replacement, 来代替第一个匹配的replacement,substring
${string//substring/replacement} 使用replacement,代替所有匹配的replacement, 代替所有匹配的replacement,substring
${string/#substring/replacement} 如果string的∗前缀∗匹配string的*前缀*匹配stringsubstring, 那么就用replacement来代替匹配到的replacement来代替匹配到的replacementsubstring
${string/%substring/replacement} 如果string的∗后缀∗匹配string的*后缀*匹配stringsubstring, 那么就用replacement来代替匹配到的replacement来代替匹配到的replacementsubstring
#shell变量的截取Shell中的${}##和%%
假设定义了一个变量为:
代码如下:
file=/dir1/dir2/dir3/my.file.txt
//变量的删除
可以用${ }分别替换得到不同的值:
${file#*/}:删掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删掉最后一个 / 及其左边的字符串:my.file.txt
${file#*.}:删掉第一个 . 及其左边的字符串:file.txt
${file##*.}:删掉最后一个 . 及其左边的字符串:txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个 / 及其右边的字符串:(空值)
${file%.*}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
记忆的方法为:
# 是 去掉左边(键盘上#在 $ 的左边)
%是去掉右边(键盘上% 在$ 的右边)单一符号是最小匹配;两个符号是最大匹配
${file:0:5}:提取最左边的 5 个字节:/dir1
${file:5:5}:提取第 5 个字节右边的连续5个字节:/dir2
//变量的替换
${file/dir/path}:将第一个dir 替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir 替换为 path:/path1/path2/path3/my.file.txt${LINE%%*}的意思就是从LINE这个变量的值中,从后面开始以最长匹配删去%%后面的表达式内容。
看一下man bash可以找到详细说明,查找Parameter Expansion这段会看到:
${parameter%word}
${parameter%%word}
都是从parameter的最后开始删除word所匹配的内容,%是最短匹配,%%是最长匹配。

实例

取得字符串长度
string=abc12342341          //等号二边不要有空格  
echo ${#string}             //结果11  
expr length $string         //结果11  
expr "$string" : ".*"       //结果11 分号二边要有空格,这里的:根match的用法差不多  
字符串所在位置
expr index $string '123'    //结果4 字符串对应的下标是从1开始的   str="abc"  
expr index $str "a"  # 1  
expr index $str "b"  # 2  
expr index $str "x"  # 0  
expr index $str ""   # 0   
从字符串开头到子串的最大长度
expr match $string 'abc.*3' //结果9 
字符串截取
  • ${varible:n1:n2}:截取变量varible从n1到n2之间的字符串
string=abc12342341
echo ${string:4}      //2342341  从第4位开始截取后面所有字符串    
echo ${string:3:3}    //123      从第3位开始截取后面3位    
echo ${string:3:6}    //123423   从第3位开始截取后面6位    
echo ${string: -4}    //2341  :右边有空格   截取后4位    
echo ${string:(-4)}   //2341  同上    
expr substr $string 3 3   //123  从第3位开始截取后面3位    
str="abcdef"  
expr substr "$str" 1 3  # 从第一个位置开始取3个字符, abc  
expr substr "$str" 2 5  # 从第二个位置开始取5个字符, bcdef   
expr substr "$str" 4 5  # 从第四个位置开始取5个字符, def  echo ${str:2}           # 从第二个位置开始提取字符串, bcdef  
echo ${str:2:3}         # 从第二个位置开始提取3个字符, bcd  
echo ${str:(-6):5}      # 从右边算起第6个字符位置向左提取5个字符串, abcde  
echo ${str:(-4):3}      # 从右边算起第4个字符位置向左提取3个字符串, cde  
匹配显示内容
# 例3中也有match和这里的match不同,上面显示的是匹配字符的长度,而下面的是匹配的内容 
string=abc12342341
expr match $string '\([a-c]*[0-9]*\)'  //abc12342341    
expr $string : '\([a-c]*[0-9]\)'       //abc1    
expr $string : '.*\([0-9][0-9][0-9]\)' //341 显示括号中匹配的内容    
截取不匹配的内容
string=abc12342341
echo ${string#a*3}     //42341  从$string左边开始,去掉最短匹配子串    
echo ${string#c*3}     //abc12342341  这样什么也没有匹配到    
echo ${string#*c1*3}   //42341  从$string左边开始,去掉最短匹配子串    
echo ${string##a*3}    //41     从$string左边开始,去掉最长匹配子串    
echo ${string%3*1}     //abc12342  从$string右边开始,去掉最短匹配子串    
echo ${string%%3*1}    //abc12     从$string右边开始,去掉最长匹配子串    
str="abbc,def,ghi,abcjkl"  
echo ${str#a*c}     # 输出,def,ghi,abcjkl  一个井号(#) 表示从左边截取掉最短的匹配 (这里把abbc字串去掉)  
echo ${str##a*c}    # 输出jkl,             两个井号(##) 表示从左边截取掉最长的匹配 (这里把abbc,def,ghi,abc字串去掉)  
echo ${str#"a*c"}   # 输出abbc,def,ghi,abcjkl 因为str中没有"a*c"子串  
echo ${str##"a*c"}  # 输出abbc,def,ghi,abcjkl 同理  
echo ${str#*a*c*}   # 空  
echo ${str##*a*c*}  # 空  
echo ${str#d*f)     # 输出abbc,def,ghi,abcjkl,   
echo ${str#*d*f}    # 输出,ghi,abcjkl     echo ${str%a*l}     # abbc,def,ghi  一个百分号(%)表示从右边截取最短的匹配   
echo ${str%%b*l}    # a             两个百分号表示(%%)表示从右边截取最长的匹配  
echo ${str%a*c}     # abbc,def,ghi,abcjkl    # 注意:这里要注意,必须从字符串的第一个字符开始,或者从最后一个开始,可以这样记忆, 井号(#)通常用于表示一个数字,它是放在前面的;百分号(%)卸载数字的后面; 或者这样记忆,在键盘布局中,井号(#)总是位于百分号(%)的左边(即前面) 
匹配并且替换
echo ${string/23/bb}   //abc1bb42341  替换一次    
echo ${string//23/bb}  //abc1bb4bb41  双斜杠替换所有匹配    
echo ${string/#abc/bb} //bb12342341   #以什么开头来匹配,根php中的^有点像    
echo ${string/%41/bb}  //abc123423bb  %以什么结尾来匹配,根php中的$有点像   
str="apple, tree, apple tree"  
echo ${str/apple/APPLE}   # 替换第一次出现的apple  
echo ${str//apple/APPLE}  # 替换所有apple  echo ${str/#apple/APPLE}  # 如果字符串str以apple开头,则用APPLE替换它  
echo ${str/%apple/APPLE}  # 如果字符串str以apple结尾,则用APPLE替换它  
$ test='c:/windows/boot.ini'  
$ echo ${test/\//\\}  
c:\windows/boot.ini  
$ echo ${test//\//\\}  
c:\windows\boot.ini  #${变量/查找/替换值} 一个“/”表示替换第一个,”//”表示替换所有,当查找中出现了:”/”请加转义符”\/”表示。  
比较
[[ "a.txt" == a* ]]        # 逻辑真 (pattern matching)  
[[ "a.txt" =~ .*\.txt ]]   # 逻辑真 (regex matching)  
[[ "abc" == "abc" ]]       # 逻辑真 (string comparision)   
[[ "11" < "2" ]]           # 逻辑真 (string comparision), 按ascii值比较  
四种判断方式 ==  !=  < > (按ascii值比较大小)
str="abcd"
if [ "$str"x == "abcdsd"x ]; then# 注:比较字符串是否相等,可以字符串后面+上个别的字符,如果str为空的话,可以防止表达式报错:[: =: unary operator expected  检测字符串是否为空
-z  检测字符串长度是否为0,为0返回 true。  [ -z $a ] 返回 false。
-n  检测字符串长度是否为0,不为0返回 true。 [ -n "$a" ] 返回 true。
str 检测字符串是否为空,不为空返回 true。   [ $a ] 返回 true。
连接
s1="hello"  
s2="world"  
echo ${s1}${s2}   # 当然这样写 $s1$s2 也行,但最好加上大括号  
[root@VM-0-3-centos ~]# t1="t1"
[root@VM-0-3-centos ~]# t2="t2"
[root@VM-0-3-centos ~]# echo ${t1} + ${t2}
t1 + t2
字符串删除
$ test='c:/windows/boot.ini'  
$ echo ${test#/}  
c:/windows/boot.ini  
$ echo ${test#*/}  
windows/boot.ini  
$ echo ${test##*/}  
boot.ini  $ echo ${test%/*} 
c:/windows 
$ echo ${test%%/*} #${变量名#substring正则表达式}从字符串开头开始配备substring,删除匹配上的表达式。 
#${变量名%substring正则表达式}从字符串结尾开始配备substring,删除匹配上的表达式。 
#注意:${test##*/},${test%/*} 分别是得到文件名,或者目录地址最简单方法。   

运算符

算数运算符

算数运算符 意义(*表示常用)
+、— 加法(或正号)、减法(或负号) *
*、/、% 乘法、除法、取余(取模) *
** 幂运算 *
++、– 增加及减少,可前置,可放在变量后,默认步长1 *
!、&&、|| 逻辑非(取反)、逻辑与(and)、逻辑或(or) *
<、<=、>、>=
==、!=、= 比较符号(相等、不相等,对于字符串‘=’也可以表示相当) *
<<、>> 向左移位、向右移位
~、|、&、^ 按位取反、按位异或、按位与、按位或
=、+=、-=、*=、/=、%=
1. i=`expr $i + 1`;
2. let i+=1;
3. ((i++));((i=$j*$k)) 
4. i=$[$i+1];
5. i=$(( $i + 1 ))  ((i=$i+1))
6. ((i=$j+$k))    等价于 i=`expr $j + $k`row=$(cat file.txt|wc -l)
#col=$[$(cat file.txt|tr -s " " "\n"|wc -l)/row]
col=$(($(cat file.txt|tr -s " " "\n"|wc -l)/row))
echo $col# bc计算,scale表示精度,这里scale=2表示小数点后面保留两位,一般的加减乘除,这个小数点的保留是根据输入数据的精度来算的,取最长精度那个echo "scale=2;100.00+10.55" | bc echo "scale=2;100.00*10.55" | bc #!/bin/bash
a=38
b=99percent_1=$(printf "%d%%" $((a*100/b)))
# 或者
percent_2=`awk 'BEGIN{printf "%.1f%%\n",('$a'/'$b')*100}'`  #变量需要加单引号
# 保留1位小数,四舍五入
echo $percent_1
echo $percent_2
运算操作符/运算命令 说明
(( )) 用于整数运算,效率很高,推荐使用
let 用于整数运算,和 (()) 类似。
[$] 用于整数运算,不如 (()) 灵活。
expr 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。
bc Linux下的一个计算器程序,可以处理整数和小数。Shell 本身只支持整数运算,想计算小数就得使用 bc 这个外部的计算器。
declare -i 将变量定义为整数,然后再进行数学运算时就不会被当做字符串了。功能有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算、自增自减等,所以在实际开发中很少使用。
  • 输入:整数,let和expr都无法进行浮点运算,但是bc和awk可以
  • 输出:bc、expr可直接显示计算结果;let则丢弃计算结果,可通过传递结果到变量,取变量值获得计算结果。

关系(比较运算符)

在[]中使用的比较符 在(()和[[]]}中使用的比较符 说明 举例
-eq == equal的缩写,表相等 [ $a -eq $b ] 返回 false
-ne != not equal的缩写,表不相等 [ $a -nq $b ] 返回 true
-gt > greater thanl的缩写,表大于 [ $a -gt $b ] 返回 false
-ge >= greater equal的缩写,表大于等于 [ $a -ge $b ] 返回 false
-lt < greater equal的缩写,表小于 [ $a -lt $b ] 返回 true
-le <= greater equal的缩写,表小于等于 [ $a -lq $b ] 返回 true

逻辑运算符

在[ ]中使用的比较符 在(()和[[]]}中使用的比较符 说明 举例
赋值 a=10 b=25
-a && 与运算,两个表达式都为true,才返回true [ $a -lt 20 -a $b -gt 20 ] 返回true
-o || 或运算,有一个表达式都为true,则返回true [ $a -lt 20 -o $b -gt 100 ] 返回true
! ! 非运算,表达式为true,则返回false [ ! false ] 返回true
# [  exp1  -a exp2  ] = [[  exp1 && exp2 ]] = [  exp1  ]&& [  exp2  ] = [[ exp1  ]] && [[  exp2 ]]# [  exp1  -o exp2  ] = [[  exp1 || exp2 ]] = [  exp1  ]|| [  exp2  ] = [[ exp1  ]] || [[  exp2 ]]# if [[ "a" == "a" && 2 -gt1 ]] ;then echo "ok" ;fi
# if [[ "a" == "a" ]] && [[2 -gt 1 ]] ;then echo "ok" ;fi
# if [[ "a" == "a" ]] || [[ 2 -gt 1]] ;then echo "ok" ;fi
# if [[ "a" == "a" ]] || [[ 2 -gt10 ]] ;then echo "ok" ;fi
# if [[ "a" == "a"  || 2 -gt 10 ]] ;then echo "ok" ;fi# Ø  [[ ]] 和 [ ] 都可以和 ! 配合使用
# 优先级 !  >  && > || # 逻辑运算符  < 关系运算符# 逻辑运算符  : !  &&  || -a  -o# 关系运算符  : <  >  \> \<  ==  = !=  – eq –ne  -gt -ge  –lt  -le
# [[  ]] 比[ ] 具备的优势
1. [[bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。在[[]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换
2. 支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。[[ ]] 中匹`配字符串或通配符,不需要引号`
3. 使用[[ ... ]]条件判断结构,而不是[... ],能够防止脚本中的许多逻辑错误。比如,&&||<> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出`现在[ ]结构中的话,会报错`
4. bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码

&&和||

&& 和 ||

# &&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执行这个命令”语法格式如下:command1 && command2 [&& command3 ...]1 命令之间使用 && 连接,实现逻辑与的功能。
2 只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。
3 只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。# command1 || command2,||则与&&相反。如果||左边的命令(命令1)未执行成功,那么就执行||右边的命令(命令2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令
1 命令之间使用 || 连接,实现逻辑或的功能。
2 只有在 || 左边的命令返回假(命令返回值 $? == 1),|| 右边的命令才会被执行。这和 c 语言中的逻辑或语法功能相同,即实现短路逻辑或操作。
3 只要有一个命令返回真(命令返回值 $? == 0),后面的命令就不会被执行

&& 和 || 在$?返回区别

# && 和 || 在$?返回区别# &&只要有一个错误,其$?就是非0
[root@jiayu ~]# cat test.sh 
#!/bin/bash
true && false
[root@jiayu ~]# bash test.sh 
[root@jiayu ~]# echo $?
1
================
# ||只要有一个错误,其$?就是0
[root@jiayu ~]# cat test.sh 
#!/bin/bash
true || false
[root@jiayu ~]# bash test.sh 
[root@jiayu ~]# echo $?
0

字符串运算符

字符串运算符 说明 举例
= 检测两个字符串是否相等,相等则返回true [ $a = $b ] 返回true
!= 检测两个字符串是否相等,不相等则返回true [ $a != $b ] 返回true
-z 检测字符串长度是否为0,为0则返回true [ -z $b ] 返回true
-n 检测字符串长度是否为0,不为0则返回true [ -n $b ] 返回true
str 检测字符串是否为null,不为null则返回true [ $b ] 返回true

文件测试运算符

man test 查看帮助

测试标志 意义 示例
-e、-a 文件名是否存在 test -e filename,存在文件名filename,返回true。
-f 文件名是否存在且为文件 test -f filename,存在文件名filename且为文件,返回true。
-d 文件名是否存在且为目录 test -d filename,存在文件名filename且为目录,返回true。
-b 文件名是否存在且为一个block device设备 test -b filename,存在文件名filename且为一个block device设备,返回true。
-c 文件名是否存在且为一个character device设备 test -c filename,存在文件名filename且为character device设备,返回true。
-S 文件名是否存在且为一个Socket文件 test -S filename,存在文件名filename且为一个Socket文件,返回true。
-p 文件名是否存在且为一个FIFO(pipe)文件 test -p filename,存在文件名filename且为FIFO(pipe)文件,返回true。
-L 文件名是否存在且为一个连接文件 test -L filename,存在文件名filename且为一个连接文件,返回true。
-r 文件名是否存在且具有“可读”权限 test -r filename,存在文件名filename且具有“可读”属性,返回true。
-w 文件名是否存在且具有“可写”权限 test -w filename,存在文件名filename且具有“可写”属性,返回true。
-x 文件名是否存在且具有“可执行”权限 test -x filename,存在文件名filename且具有“可执行”属性,返回true。
-u 文件名是否存在且具有“SUID”属性 test -u filename,存在文件名filename且具有“SUID”属性,返回true。
-g 文件名是否存在且具有“SGID”属性 test -g filename,存在文件名filename且具有“SGID”属性,返回true。
-k 文件名是否存在且具有“Sticky bit”属性 test -k filename,存在文件名filename且具有“Sticky bit”属性,返回true。
-s 文件名是否存在且为“非空白文件” test -s filename,存在文件名filename且为非空白文件,返回true。
-nt 前者是否比后者新(根据修改日期) test filename1 -nt filename2,filename1比filename2新或者filename1存在filename2不存在,返回true。
-ot 前者是否比后者旧(根据修改日期) test filename1 -ot filename2,filename1比filename2旧或者filename2存在filename1不存在,返回true。
-ef 是否为同一文件,可用在硬链接上。判断两个文件是否指向同一个inode test filename1 –ef filename2,filename1与filename2为同一文件,返回true。
-h、-L 文件存在且是符号链接 test -h filename,存在文件名为filname且为符号链接,返回true。
-t 文件描述符打开并指向终端 test -t fd,如果文件描述符fd打开并指向终端,返回true。
-G 文件存在且属于有效组id test -G filename,存在文件名为filname,且属于有效组id,返回true。
-N 文件存在且在最后一次读取后有修改 test -N filename,存在文件名为filname,且在最后一次读取后有修改,返回true。
-O 文件存在且属于有效用户id test -O filename,存在文件名为filname,且属于有效用户id,返回true。

运算命令汇总

运算操作符与运算命令 意义(*为推荐)
(()) 用于整数运算的常用运算符,效率高 *
let 用于整数运算,类似(()) *
expr 可用于整数运算,但还有其他额外功能
bc Linux下的一个计算器程序,适合整数及小数运算
$[] 用于整数运算
awk awk即可用于整数运算,也可用于小数运算 *
declare 定义变量属性值和属性,-i参数可用于定义整数形变量,做运算
echo $((a+1)) #这里面的a不用加$
let i=$a+1
expr 2 + 2  #前后都有空格
expr 2 \* 2
echo $[2+2]
echo 1.1+2.3|bc

(())

双小括号(())的作用是进行数值运算与数值比较,它的效率很高,是常采用的

运算操作符与运算命令 意义
((i=i+1)) 这种书写方法为运算后赋值法,即将i+1的运算结果赋值给变量i,注意不能用"echo ((i=i+1))"的形式输出表达式的值,但可以用echo $((i=i+1))输出其值
i=$((i+1)) 可以在(())前加$符,表示将表达式运算后赋值给i
((8>7&&5==5)) 可以进行比较,还可以加入逻辑与和逻辑或,用户条件判断
echo $((2+1)) 需要直接输出表达式的运算结果时,可以在(())前加$符
# --,++((a++)),((a--)),((++a)),((--a))# $()赋值myvar=$((i+1))myvar=$(($a*$b))

let

# let运算命令的语法格式为:let 赋值表达式
# let赋值表达式的功能等同于((赋值表达式))let i=i+8

expr

# 语法:expr Expressionexpr 2 + 2expr 2 \* 2expr 2 / 2注意:运算符及用于计算的数字左右都至少有一个空格,否则会报错使用乘号时,必须使用反斜线屏蔽其特定含义,因为shell可能会误解*的含义# expr 配合变量计算i=`expr $i + 6`# expr 判断文件类型expr "text.sh" : ".*\.sh"  &>/dev/null && echo "yes"||echo "no"# 判断是否为整数expr 1 + $1 &>/dev/null && echo "yes"||echo "no" #注意1 + $1和加号之间的空格 可以判断$1 是不是整数# 计算字符串长度

bc

	echo 3+5|bcecho 'scale=2;355/1133'|bc #scale保留二位小数i=`echo $i+6|bc` #利用echo输出表达式,通过管道交给bc计算,次方法效率低下

awk计算

echo "3 9"|awk '{print ($1+3)*$2}' #3,9中间要空格隔开

declaare(同typeset)命令的用法

declare -i A=30 B=7 #declare -i 参数可以将变量定义为整形
A=A+B
echo $A

$[]

echo $i
echo $[2*3]
i=$[i+6]

条件判断

简介

test,[],[[]]用法:help test,man test即可得到帮助,完整的[],[[]]用法可以通过man bash来获取

条件测试语法 说明
test <测试表达式> 这是利用 test命令进行测试条件表达式的方法。test命令和<测试表达式>之间至少有一个空格
[ <测试表达式> ] 这是通过[]进行条件表达式测试的方法,和test命令的用法相同,[]的边界之间至少有一个空格
[[ <测试表达式> ]] 这个通过[[]]测试条件表达式的方法,是比test和[]更新的语法格式,[[]]的边界和内容之间至少有一个空格
((<测试表达式>)) 通过(())进行测试条件表达式的方法,一般用于if语句中,(())双小括号二端不需要有空格
() 通过小括号实现测试条件的表达式方法,应用不普及
`` 应用不普及
# 第一种test命令和第二种的[]是等价的,第三种的[[]]为扩展的test命令,语法4中的(())常用户计算# 在[[]](双中号)中可以使用通配符等进行模式匹配,这是区别于其他几种语法格式的地方# &&,||,<,>等操作可以应用于[[]]中,但不能用于[]中,在[]中一般用-a,-o,-gt等# 对于整数推荐(())# 有关[],[[]],(())用法小结:整数加双引号的比较是对的[[]]中用类似-eq等的写法是对的,[[]]中用类似<,>的写法也可能不对,有可能会只比较第一位,逻辑结果不对[]中用类似>,<的写法在语法上虽然可能没有错,但是逻辑结果不对,可以使用=,!=正确比较(())中不能使用-eq类似写法,但是可以使用类似>,<的写法
# 条件判断中可使用通配符
if [[ "${DB_FILE}" == *".gz" ]]; then# 注意下这种恢复方式gunzip <${DB_FILE} | docker run --rm -i --network=jms_net jumpserver/mysql:5 ${restore_cmd}elsedocker run --rm -i --network=jms_net jumpserver/mysql:5 $restore_cmd <"${DB_FILE}"
fi# 命令行运行[ -f test.sh ] || echo 0[[ -f test.sh ]] || echo 0
# 逻辑判断[ -f xx -o -f ] && echo 1 || echo 0[ -f xx && -f xx ] #这是错误的,不能使用&&或者||[-f xx ] && [ -f xxx ] && [ ] #这样的可以[[ ! -n "$a" && "$a" = "$b" ]][[ -z "$a" || "$a" != "$b" ]] #[[]]中能使用&&,||[[ -z "$a" -a "$a" != "$b" ]] #[[]]中不能使用 -a,-o((m>20&&n>20)) #内部用&&或||((m<20 -a n<30)) #内部用-a,-o会报错注意:-a和-o逻辑操作符号需要用于[]&&||逻辑操作符号可用户[[]](())中,也可以在外部连接多个[][[]],[]的二端及比较符号的二端,必须要有空格,但是(())不需要

高级用法

# 用法一:if 中直接grep
[root@VM-0-3-centos ~]# cat 1.sh 
#!/bin/bash 
if [ `grep "root:/bin/bash" /etc/passwd` ]; thenecho 1
fi
[root@VM-0-3-centos ~]# bash 1.sh 
1
# 用法二:当if语句中的命令返回非零退出状态码时,会执行else部分中的命令,else部分可以包含多条命令
[root@VM-0-3-centos ~]# cat 1.sh 
#!/bin/bash 
if ls -l /etc/passwd | grep boy ; thenecho ok
elseecho no
fi
# 用法三:if中调用其他脚本,其他脚本返回的exit来进行if判断  基于exit判断
[root@boy ~]# cat 1.sh 
#!/bin/bash 
if bash /root/2.sh ; thenecho ok
elseecho false
fi
[root@boy ~]# cat 2.sh 
#!/bin/env bash
exit 1
[root@boy ~]# bash 1.sh 
false
==============================================================
# 基于return判断
[root@boy ~]# cat 1.sh 
#!/bin/bash 
if bash /root/2.sh ; thenecho ok
elseecho false
fi[root@boy ~]# cat 2.sh 
#!/bin/env bash
function main(){return 1
}
main[root@boy ~]# bash 1.sh 
false================================================================
[root@boy ~]# cat 2.sh         #可见return 覆盖了$?
#!/bin/env bash
function main(){return 1
}
main
echo $?
[root@boy ~]# bash 2.sh 
1

流程控制

命令 说明
break n 如果省略n,表示跳出整个循环,n 表示跳出循环的层数
continue n 如果省略n,则表示跳出本次循环,忽略本次循环的剩余代码,进入循环的下一次循环。n表示退到第n层继续循环
exit n 退出当前shell程序,n为上一次程序执行时的返回值,n 也可以省略,在下一个shell里可以通过$?来接收exit n的 n值
return n return命令允许带一个整型参数, 这个整数将作为函数的"退出状态码"返回给调用这个函数的脚本, 并且这个整数也被赋值给变量$?

for

in列表可以包含替换、字符串和文件名。
for var in item1 item2 ... itemN
docommand1command2...commandN
done#!/bin/bash
for i in 1 2 3
doif [ $i -eq 1 ]; thencontinuefiecho $i
done
#!/bin/bashfor i in $(find /etc/ -type f -mtime +7) #如果没有find到文件,则会什么都没有输出
doecho $isleep 1
done

while

while condition
docommand
done
############################################################################
until condition
docommand
done

while死循环的几种写法:

while true
do语句
done
while :
do语句
done
while [1]
do语句
done
while [0]
do语句
done

case

casein
模式1)command1command2...commandN;;
模式2)command1command2...commandN;;
esac

select

数组

参考:https://www.cnblogs.com/mydriverc/p/8302841.html

定义

  • 数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。
  • 使用@ 或 * 可以获取数组中的所有元素,例如:
#定义方法
1. array_name=(value1 value2 ... valuen)2. array_name=([0]=one [1]=two [2]=three)3. array_name[0]=onearray_name[1]=two4. 命令的结果放到数组array_name=(`ls /server/scripts`)array_name($(ls /server/scripts))# 第一种生成的结果并不是数组PORT_TEST=`ss -lntup |grep -w "nc" |awk -F '[ :]+' '$6~/^[0-9]+$/{print $6}'`PORT_LIST=(`ss -lntup |grep -w "nc" |awk -F '[ :]+' '$6~/^[0-9]+$/{print $6}'`)5. 分别定义(不推荐使用)array[0]=a;array[1]=b;array[2]=c# 输出全部内容echo ${array[@]}echo ${array[*]}# 输出数组长度echo ${#array[@]}echo ${#array[*]}# 增加数据内容array[5]=oldboy 下标不能重复# 删除数组内容unset array[1]unset array[*]# 从指定位置开始输出echo ${array[@]:1} #从第一个位置开始输出# 输出变量定长echo ${array[5]:1:2}## 分片:${数组名[*或@]:起始位:长度},截取部分数组,返回字符串,中间用空格分隔;将结果使用“()”,则得到新的切片数组# 只打印数组key
$ my_array=(foo bar baz)
$ for index in "${!my_array[@]}"; do echo "$index"; done012

遍历

# # 用循环输出数组中元素的下标 array=(7 3 4 6 2) for ((i=0;i<${#array[*]};i++)) do echo $i done首先创建一个数组 array=( A B C D 1 2 3 4)
# 标准的for循环
for(( i=0;i<${#array[@]};i++)) do#${#array[@]}获取数组长度用于循环echo ${array[i]};
done;# for … in
# 遍历(不带数组下标):
for element in ${array[@]}#也可以写成for element in ${array[*]}
doecho $element
done# 遍历(带数组下标): 只打印key
for i in "${!arr[@]}";   
do   printf "%s\t%s\n" "$i" "${arr[$i]}"  
done# While循环法:
i=0  
while [ $i -lt ${#array[@]} ]  
#当变量(下标)小于数组长度时进入循环体
do  echo ${ array[$i] }  #按下标打印数组元素let i++  
done 

判断是否在数组

比如定义数组:arr=("one" "tow" "thr" "three" "four")# 模糊匹配,也可以理解为子集匹配,优点是比较简洁,缺点是匹配不精确。比如参数为:th, thr, thre, three 均满足执行条件
if [[ "${arr[*]}" =~ ${var} ]]; then
# do something
fi# 判断字符串是否存在一个文本中。
#!/bin/sh
names="This is a computer , I am playing games in the computer"
if [[ "${names[@]}" =~ "playing" ]]; thenecho '字符串存在'
fi# 精准匹配,需要函数实现
function contains() {local n=$#local value=${!n}for ((i=1;i < $#;i++)) {if [ "${!i}" == "${value}" ]; thenecho "y"return 0fi}echo "n"return 1
}A=("one" "two" "three four")
if [ $(contains "${arr[@]}" "thre") == "y" ]; thenecho "contains thre"
fi
if [ $(contains "${arr[@]}" "three") == "y" ]; thenecho "contains three"
fi

函数

[ function ] funname [()] #这里有三种形式,注意{action;[return int;]}
############################################################################
#!/bin/bashdemoFun(){echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"############################################################################
#!/bin/bash
funWithParam(){echo "第一个参数为 $1 !"echo "第二个参数为 $2 !"echo "第十个参数为 $10 !"echo "第十个参数为 ${10} !"echo "第十一个参数为 ${11} !"echo "参数总数有 $# 个!"echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
#!/bin/bash
EXE=""function main(){EXE="TEST"
}
main
echo $EXE  #输出TEST

shell获取函数返回值

如果函数体中没有return语句,那么使用默认的退出状态,也就是最后一条命令的退出状态,如果这就是你想要的,那么更加严谨的写法为:return$?

# 方式1
原理:return返回的值可以通过$?得到。
缺点:return只能返回整数
#!/bin/sh  
function test()  
{return 100
}
test
echo $?#方式2(这种方式中,函数本体如果没被调用,函数里面的输出不会显示)
原理:函数内部使用echo输出,调用函数时将输出结果捕获。
缺点:只能输出与返回值相关的内容,且所有使用到的命令(如grep)一定要记得2>&1输出到空设备。
#!/bin/sh  
function test()  
{echo 100  
}
echo $(test)#方案3
原理:全局变量
#!/bin/bash
g_var=
function test()
{g_var=100
}
test
echo "g_var=$g_var"#重点
=======================================================================================
#./test.sh start执行后,只有start输出,main函数中没输出#!/bin/bash
function main(){echo "xxxxxxxxxxxxxxxxxxxxxxx"echo "this is $1"
}
echo $1
test=$(main "main")-------------------------------------------------------
#./test.sh start 执行后,有start,和main函数输出#!/bin/bash
function main(){echo "xxxxxxxxxxxxxxxxxxxxxxx"echo "this is $1"
}
echo $1
test=$(main "main")
echo $test
# echo函数返回值
[root@boy ~]# cat 1.sh 
#!/bin/bash 
function test(){echo 1
}
test
[root@boy ~]# bash 1.sh 
1
===============================================
[root@boy ~]# cat 1.sh 
#!/bin/bash 
function test(){echo 1
}
re=$(test)
echo $re
[root@boy ~]# bash 1.sh 
1

进程相关

用法 说明
sh test.sh 把脚本放到后台执行(后台运行脚本常用)
nohub test.sh & 使用nohub把脚本放到后台执行
ctr+c 停止当前脚本
ctr+z 暂停当前脚本
bg 把当前脚本或任务放到后台执行
fg 把当前脚本或任务放到前台执行,如果有多个任务,可以使用fg加任务编号调用出相应脚本任务
jobs 查看当前执行的脚本任务
kill 关闭执行的脚本任务,"kill %任务编号"的形式关闭脚本,这个任务标号可以通过jobs获得

正则表达式

参考链接:https://www.jb51.net/tools/shell_regex.html

  • 正则表达式用来在文件中匹配符合条件的字符串,正则包含匹配。grep、awk、sed等命令可以支持正则表达式
  • 通配符用来匹配符合条件的文件名,通配符是完全匹配,ls,find,cp这些命令不支持正则表达式,所以只能使用shell自己的通配符来进行匹配了
\ 建一个字符标记为一个特殊字符,或一个原义字符,比如,”n”匹配字符“n”, “/n”匹配一个换行符,序列”\”匹配”\”而“(”匹配”(“。
^ 匹配输入字符串的开始位置
$ 匹配输入字符串的结束位置
{n} n是一个非负整数,匹配确定的n次,例如:”o{2}”不能匹配“Bob”种的“o”,但是能匹配“book”种的两个”o”
{n,} n是一个非负整数,至少匹配n次,例如:“o{2,}“不能匹配‘bob’,但可以匹配”foooood“种的所有‘o’, "o{1,}"等价于”o+“,"o{0,}"等价于”o*“
{n,m } m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次,比如“o{1,3}”将匹配“fooooodv”种的前三个o
* 匹配前面的子表达式零次或多次
+ 匹配前面的子表达式一次或多次
? 匹配前面的子表达式零次或一次
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除了”\n“之外的任何单个字符,要匹配包括“\n”在内的任何字符,请使用像“(.|\n)”的模式。
(pattern) 匹配pattern并获取这一匹配的子字符串。该子字符串用户向后引用。要匹配圆括号字符,请使用“(”或“)”。
x竖线y 匹配x或y。 例如”z竖线food“能匹配”z“或”food”.”(z竖线f)oood”则匹配”zood“或”food“。
[xyz] 字符集合(character class)。匹配所包含的任意一个字符。例如,”[abc]“可以匹配”plain“中的”a”。其中特殊字符仅有反斜线\保持特殊含义,用于转义字符。其它特殊字符如星号、加号、各种括号等均作为普通字符。脱字符^如果出现在首位则表示负值字符集合;如果出现在字符串中间就仅作为普通字符。连字符 – 如果出现在字符串中间表示字符范围描述;如果如果出现在首位则仅作为普通字符。
[^xyz] 排除型(negate)字符集合。匹配未列出的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
特殊符号 说明
[:alnum:] 代表英文大小写字母及数字,亦即 0-9, A-Z, a-z
[:alpha:] 代表任何英文大小写字母,亦即 A-Z, a-z
[:blank:] 代表空白键与 [Tab] 按键两者
[:cntrl:] 代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del… 等等
[:digit:] 代表数字而已,亦即 0-9
[:graph:] 除了空白字节 (空白键与 [Tab] 按键) 外的其他所有按键
[:lower:] 代表小写字母,亦即 a-z
[:print:] 代表任何可以被列印出来的字符
[:punct:] 代表标点符号 (punctuation symbol),亦即:” ’ ? ! ; : # $…
[:upper:] 代表大写字母,亦即 A-Z
[:space:] 任何会产生空白的字符,包括空白键, [Tab], CR 等等
[:xdigit:] 代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字节
# if [[ $aexp =~ all ]],其中 ~是对后面的正则表达式匹配的意思,如果匹配就输出1,不匹配就输出0双目运算符=~;它和==以及!=具有同样的优先级。如果使用了它,则其右边的字符串就被认为是一个扩展的正则表达式来匹配。如果字符串和模式匹配,则返回值是0,否则返回1。如果这个正则表达式有语法错误,则整个条件表达式的返回值是2。如果打开了shell的nocasematch 选项则匹配时不考虑字母的大小写。模式的任何部分都可以被引用以强制把其当作字符串来匹配。由正则表达式中括号里面的子模式匹配的字符串被保存在数组变量BASH_REMATCH 中。BASH_REMATCH 中下标为0的元素是字符串中与整个正则表达式匹配的部分。BASH_REMATCH 中下标为n的元素是字符串中与第n 个括号里面的子模式匹配的部分
#!/bin/bashSTR="i love linux"if [[ $STR =~ "love" ]]; thenecho 1
fiif [[ ! $STR =~ "nt" ]]; thenecho 2
fi# 结果
[root@boy ~]# bash test.sh 
1
2

通配符

参数

|     #管道符,或者(正则)
>     #输出重定向
>>    #输出追加重定向
<     #输入重定向
<<    #追加输入重定向
~     #当前用户家目录
`` $() #引用命令被执行后的结果
$     #以。。。结尾(正则)
^     #以。。。开头(正则)
*     #匹配全部字符,通配符#任意一个字符,通配符
#       #注释
&       #让程序或脚本切换到后台执行
&&      #并且 同时成立
[]      #表示一个范围(正则,通配符)
{}      #产生一个序列(通配符)
.       #当前目录的硬链接
符号 作用
* 匹配任何字符串/文本,包括空字符串;*代表任意字符(0个或多个) ls file *
? 匹配任何一个字符(不在括号内时)?代表任意1个字符 ls file 0
[abcd] 匹配abcd中任何一个字符
[a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符 ls file 0
{…} 表示生成序列. 以逗号分隔,且不能有空格
补充
[!abcd] 或[^abcd]表示非,表示不匹配括号里面的任何一个字符

? 任何一个字符

[root@localhost ~]# ls /sbin/???
/sbin/arp  /sbin/cbq  /sbin/lid  /sbin/lvm  /sbin/mtr  /sbin/sln  /sbin/zic
/sbin/atd  /sbin/gdm  /sbin/lpc  /sbin/lvs  /sbin/pvs  /sbin/vgs

[abcd]

# [abcd]表示匹配中括号内任意一个字符就成立
[root@localhost ~]# ls /sbin/l[vm]m
/sbin/lvm

{}

生成序列

[root@localhost ~]# touch {1..10}
[root@localhost ~]# ls
1  10  2  3  4  5  6  7  8  9  anaconda-ks.cfg

利用 {} 来备份

# 将ae复制一份叫做afffcp a{e,fff}# 将ae备份叫做ae.bakcp a{e,e.bak}# 备份简写cp a{,.bak}

利用mv

mv README.{txt,md}  ~= mv README.txt README.mdmv data/{models,ml} ~= 	mv data/models data/ml

[]用来找文件

[root@localhost ~]# touch {a..g}
[root@localhost ~]# ls
a  b  c  d  e  f  g  test
[root@localhost ~]# ls [a..c]
a  c
[root@localhost ~]# ls [a-c]
a  b  c
[root@localhost ~]# touch {1..10}
[root@localhost ~]# ls
1  10  2  3  4  5  6  7  8  9  a  b  c  d  e  f  g  test
[root@localhost ~]# ls [1-10]
1

[^abcd]

!^表示非,取反