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脚本中使用
- 环境变量也称为全局变量,可以创建他们的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种:
- 通过系统用户登录后默认运行的shell
- 非登录交互式运行shell
- 执行脚本运行交互式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(全局环境变量文件)。因此如果希望在非登录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]
!^表示非,取反