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/.bashp​rofile(用户环境变量文件),在这个文件中又会去找HOME/.bashrc(用户环境变量文件),如果有,则执行,如果没有则不执行,在$HOME/.bashrc文件中又会去找/etc/bashrc(全局环境变量文件),如果有,则执行,如果没有,则不执行。

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

特殊变量

$0 当前程序的名称,即文件名
$n (n=1……9) 第n个参数比如$1 、2。n>9时:需要用大括号括起来2。n>9时:需要用大括号括起来2。n>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的*前缀*匹配string的∗前缀∗匹配substring, 那么就用replacement来代替匹配到的replacement来代替匹配到的replacement来代替匹配到的substring
${string/%substring/replacement} 如果string的∗后缀∗匹配string的*后缀*匹配string的∗后缀∗匹配substring, 那么就用replacement来代替匹配到的replacement来代替匹配到的replacement来代替匹配到的substring
#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

case 值 in
模式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]

!^表示非,取反

shell语法入门看着一篇就够了相关推荐

  1. 关于uni-app入门看完这篇就够了

    关于uni-app的入门 前言 这是一篇关于uni-app入门的文章,也是我对uni-app的总结与归纳,刚开始的时候在这个平台上面找寻同类型的文章的时候发现讲的都很片面不怎么详细,所以就写了一篇关于 ...

  2. mybatis-plus学习(一)——入门看这一篇就够了

    文章目录 前言 准备工作 1.需要准备的SQL 2.一个简单的maven项目 3.配置文件如下 4.建立一个简单的实体和mapper 5.简单的helloworld测试程序 基本使用 通用mapper ...

  3. 真的,关于 Kafka 入门看这一篇就够了

    作者 | cxuan 责编 | 刘静 Kafka 系列的阶段性总结(万字长文,做好准备,建议先收藏再看) 初识 Kafka 什么是 Kafka Kafka 是由 Linkedin 公司开发的,它是一个 ...

  4. 学会Linux Shell循环脚本看这一篇就够了

    前言: 循环不管在程序中还是脚本中都需要经常用到,那么在 linux 中 简单的shell 脚本怎么写循环呢?在写shell脚本时,经常需要进行循环操作.这里简单谈一下 常用的 for循环结构 然后再 ...

  5. 视频剪辑用什么软件?新手入门看这一篇就够!

    视频剪辑用什么软件可以简单地做出好看的视频?这个问题应该很多视频剪辑新手都会遇到,本篇会整理一些自己亲测好用的视频剪辑软件推荐给大家,赶紧往下看吧! 视频剪辑用什么软件 蜜蜂剪辑 右糖 爱拍 iMov ...

  6. python自动操作微信_Python+Appium 自动化操作微信入门看这一篇就够了

    简介 Appium 是一个开源的自动化测试工具,支持 Android.iOS 平台上的原生应用,支持 Java.Python.PHP 等多种语言. Appium 封装了 Selenium,能够为用户提 ...

  7. 学习Sql语法,看这一篇就够了!速成宝典,看完必懂!

    一.基本概念 数据库术语 数据库(database) - 保存有组织的数据的容器(通常是一个文件或一组文件). 数据表(table) - 某种特定类型数据的结构化清单. 模式(schema) - 关于 ...

  8. QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    目录 一.Qt概述 1.1 什么是Qt 1.2 Qt的发展史 1.3 Qt的优势 1.4 Qt版本 1.5 成功案例 二.创建Qt项目 2.1 使用向导创建 2.2 一个最简单的Qt应用程序 2.2. ...

  9. python控制手机微信_Python + Appium 自动化操作微信入门看这一篇就够了

    简介 Appium 是一个开源的自动化测试工具,支持 Android.iOS 平台上的原生应用,支持 Java.Python.PHP 等多种语言. Appium 封装了 Selenium,能够为用户提 ...

最新文章

  1. 腾讯云Linux云主机SSH远程连接
  2. fragment 使用抽屉栏的_iPhone 这些使用小技巧,我不说你可能不知道哦
  3. Spring Boot2.0之整合Redis
  4. git checkout 命令详解—— Git 学习笔记 16
  5. Linux设备驱动的分层设计思想
  6. phpmyadmin 4.8.1 Remote File Inclusion(CVE-2018-12613)远程文件包含漏洞复现
  7. Google Analytics使用说明
  8. beta:scrum5
  9. python三维模型_python三维模型
  10. clion上添加程序的预定添加程序的命令行
  11. gcc2.95.3安装过程
  12. MongoDB - 分片管理
  13. python列表去重_python 字典列表/列表套字典 去重重复的字典数据
  14. android性能调优的工具,神兵利器-Android 性能调优工具 Hugo
  15. 汇编语言浮点数指令集
  16. KDC+NFS 服务配置
  17. word在试图打开文件时遇到错误,解决办法
  18. 麒麟系统开发笔记(七):在线安装软件后,提取其安装包,部署目标机使用离线软件包方式安装软件
  19. spring quartz 实现全局任务
  20. ios隐私政策_新的iOS 14已经揭露了严重的隐私问题

热门文章

  1. 直接告诉你EndNote怎么用!
  2. 大数据简史(从结绳记事到如何成为数据科学家)
  3. 计算机毕设(附源码)JAVA-SSM基于的高速收费系统
  4. 斗兽棋详细设计说明文档
  5. snagit截图工具的官方路径
  6. 干货 汽车金融如何进行贷后管理?
  7. 【全网资源大全】 wordpress 主题模板/插件 mangento zencart opencart shopify prestashop 模板
  8. Delphi ActionList详解
  9. 小米抢购页面源码分析2014年03月04号
  10. 为车身添加花纹(每天一个PS小项目)