如果没有特殊说明,数据类型默认都是字符串。常见字符串操作有:
已知变量str=abcabc
1、切片
echo ${str:1:3} #${varName:offset:size},分页取子串
echo ${str:2} #${varName:offset},偏移offset个字符取至末端
echo ${str: -2} #${varName: -number},取末端number个字符,冒号后必须跟空格
2、查找删除
${var#pattern} #从左边开始搜索命中pattern最短的串(匹配最简单),并删除
${var##pattern} #从左边开始搜索命中pattern最长的串(匹配最贪婪),并删除
${var%pattern} #从右边开始搜索命中pattern最短的串(匹配最简单),并删除
${var%%pattern} #从右边开始搜索命中pattern最长的串(匹配最贪婪),并删除
3、查找替换
echo ${str/bc/d} #${varName/pattern/substi},查找变量中第一次匹配到的pattern字符串替换为substi
echo ${str//bc/d} #${varName//pattern/substi},查找变量中所有匹配到的pattern字符串替换为substi
如果d为空,则意味着查找删除(和前面的删除不一样,此时删除的内容可以不连续)
echo ${str/bc/} #${varName/pattern/},查找变量中第一次匹配到的pattern字符串并删除
echo ${str//bc/} #${varName//pattern/},查找变量中所有匹配到的pattern字符串全部删除
假设我们定义了一个变量为 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
str1 < str2 # str1小于str2为true,比较字母在字母表顺序,字母越靠后就越大,反之越小
str1 > str2 # str1大于str2为true,比较字母在字母表顺序,字母越靠后就越大,反之越小
str1 == str2 #
str1 != str2 #
# if [ $var1 \< $var2 ];then #实际使用特殊符号需要转义
5、空/非空处理
${varName:-value} #如果varName为空则返回value,并不会修改变量本身,输出的value是命令执行结果
${varName:=value} #如果varName为空,则返回value并赋值给varName,不为空则返回其本省值,等同于varName=${varName:-value}
${varName:+value} #如果varName为空不做处理,不为空则返回value,varName本身不变
${varName:?errorInfo} #varName如果为空则返回errorInfo,并会输出到错误输出中,不为空则返回其原值
6、其他
${varName^^} #将字符串中的小写字母转为大写字母
${varName,,} #将字符串中的大写字母转为小写字母
declare -i varName=value
declare -a arrayName #定义数组
index从0开始(至少/bin/bash是这样)
可以直接赋值并定义
arrayName[IDX]=value #单个元素赋值
arrayName=([IDX]=value [IDX]=value) #部分元素赋值
arrayName=(value1 value2 value3) #所有元素完全赋值
对于 arrayName=(/dir/file)
, value是文件目录时会读取文件列表输入到数组中, 以文件名进行排序
${arrayName[IDX]} #引用单个元素,不指定下标则表示引用第0个元素
${arrayName[@]} #引用所有元素,也可用${arrayNanme[*]}
${#arrayName[@]} #返回数组元素个数,也可用${#arrayName[*]}
${#ARRAY_NAME[INDEX]} #表示此位置的变量的字符个数
${arrayName[@]:offset:number} #从offset开始偏移number个元素
${arrayName[@]:offset} #从offset开始偏移至结尾
arrayName[${#arrayName[@]}]=value #在数组最后追加一个元素并赋值
unset arrayName[IDX] #撤销某个元素,不会将其后面的元素下标向前推进1,但是在使用${#arrName[@]}时,数组元素个数会减1,即变为稀疏数组
for e in $arr; do
echo $e
done
低版本/bin/bash不支持
关联数组使用字符串作为下标,而不是整数。关联数组也称“键值对(key-value)”数组,key为下标,value为元素值
declare -A 数组名
(必须先声明才能赋值)
#一次多个赋值
declare -A map=([jack]="18" [amy]="16" [tom]="20")
#单个赋值
map[john]="18"
echo ${map[key]} #访问“key”对应的value值
echo ${map[@]} #获取关联数组的所有元素值
echo ${!数组名[@]} #获取关联数组的所有下标值
echo ${#数组名[@]} #获取关联数组元素的个数
echo $((a+=1))
echo $((a-=1))
echo $((a*=2))
echo $((a/=2))
echo $((a%=2))
echo $((a<<=2))
echo $((a>>=2))
echo $((a&=2))
echo $((a|=2))
echo $((a^=2))
通过表达式来运算,两种方式。方式1: 通过$((表达式))
,方式2: echo "表达式" | bc
,示例:
#加减乘除
echo "$((2+6/10))" #结果为2,先算除法最后截断取整
echo "2+6/10" |bc #结果一致
#取模
echo "100%3" | bc
echo "$((100%3))"
#幂运算
echo "10^3" | bc
echo $((16<<2)) #左移
echo $((16>>2)) #右移
echo $((3&2)) #与运算
echo $((4|2)) #或运算
echo $((~2)) #非运算
echo $((6^2)) #异或
对于一个命令执行,$?
如果返回0则为true,否则为false
对于一个表达式,如果为true结果则为1,否则为0
echo $((1<0)) #小于
echo $((1<=0)) #小于等于
echo $((1>0)) #大于
echo $((1>=0)) #大于等于
echo $((1==0)) #相等
echo $((1!=0)) #不相等
echo $((1>0 && 2>0)) #逻辑与
echo $((1>0 || 2<0)) #逻辑或
echo $((!1>0)) #逻辑非
echo $((1>0?2:3)) #三元
格式为[ 条件表达式 ]
(注意前后有空格)
[ $num1 -gt $num2 ]
-gt: 大于
-lt: 小于
-ge: 大于等于
-le: 小于等于
-eq: 等于
-ne: 不等于
比较
[[ "$str1" > "$str2" ]]
>: 大于则为真
<: 小于则为真
>=:大于等于则为真
<=:小于等于则为真
==:等于则为真
!=:不等于则为真
空判断
-n String: 是否不空,不空则为真,空则为假
-z String: 是否为空,空则为真,不空则假
文件判断
-a FILE:存在则为真;否则则为假;
-e FILE:存在则为真;否则则为假;
-f FILE: 存在并且为普通文件,则为真;否则为假;
-d FILE: 存在并且为目录文件,则为真;否则为假;
-L/-h FILE: 存在并且为符号链接文件,则为真;否则为假;
-b: 存在并且为块设备,则为真;否则为假;
-c: 存在并且为字符设备,则为真;否则为假
-S: 存在并且为套接字文件,则为真;否则为假
-p: 存在并且为命名管道,则为真;否则为假
-s FILE: 存在并且为非空文件则为值,否则为假;
-r FILE:文件可读为真,否则为假
-w FILE:文件可写为真,否则为假
-x FILE:文件可执行为真,否则为假
file1 -nt file2: file1的mtime新于file2则为真,否则为假;
file1 -ot file2:file1的mtime旧于file2则为真,否则为假;
组合条件测试
与:[ condition1 -a condition2 ]
condition1 && condition2
或:[ condition1 -o condition2 ]
condition1 || condition2
非:[ -not condition ]
! condition
与:COMMAND1 && COMMAND2
COMMAND1如果为假,则COMMAND2不执行
或:COMMAND1 || COMMAND2
COMMAND1如果为真,则COMMAND2不执行
非:! COMMAND
如果只是简单的单分支或ifelse则可以直接用test命令+&&||等来执行命令
,test命令不要加方框
read -p "Input a fileName: " fileName
test -z $fileName && echo "You must input a fileName" && exit 0
test ! -e $fileName && echo "file not exist" && exit 0
test -f $fileName && fileType="regulare file"
test -d $fileName && fileType="directory"
顺序执行
:单行执行多个语句时,用分号;
隔开即可。如:ls;ls;ls
&&执行
:前一个命令执行成功(即$?
为0),后面的才执行。如:ls /tmp/abc && touch /tmp/abc/111.txt
||执行
:前一个命令执行成功后一个命令不执行,前一个命令执行失败后一个命令会执行。如:mkdir /tmp/abc && mkdir /tmp/abc
简单的ifelse执行流:ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/111.txt
#1. if语句之单分支
语句结构:
if 测试条件;then
选择分支
fi
#2、if语句之双分支
语句结构:
if 测试条件;then
选择分支1
else
选择分支2
fi
#3、if语句之多分支
语句结构:
if 条件1;then
分支1
elif 条件2;then
分支2
elif 条件3;then
分支3
...
else
分支n
fi
case 变量引用 in
PATTERN1)
分支1
;;
PATTERN2)
分支2
;;
...
*)
分支n
;;
esac
# 1、for语句格式一
for 变量名 in 列表; do
循环体
done
declare -i sum=0
for i in {1..100}; do
let sum+=$i
done
echo $sum
# 2、for语句格式二
for ((初始条件;测试条件;修改表达式)); do
循环体
done
declare -i sum=0
for ((counter=1;$counter <= 100; counter++)); do
let sum+=$counter
done
echo $sum
# 1. 语句结构
while 测试条件; do
循环体
done
declare -i counter=1
declare -i sum=0
while [ $counter -le 100 ]; do
if [ $[$counter%2] -eq 0 ]; then
let sum+=$counter
fi
let counter++
done
echo $sum
# 2. while特殊用法:遍历文本文件
语句结构:
while read 变量名; do
循环体
done < /path/to/somefile
变量名,每循环一次,记忆了文件中一行文本
while read line; do
userID=`echo $line | cut -d: -f3`
groupID=`echo $line | cut -d: -f4`
if [ $[$userID%2] -eq 0 -a $userID -eq $groupID ]; then
echo $line | cut -d: -f1,3,7
fi
done < /etc/passwd
# 1.语句结构:
until 测试条件; do
循环体
done
测试条件为假,进入循环;测试条件为真,退出循环
例:求100以内所有偶数之和,要求使用取模方法(until实现)
#!/bin/bash
declare -i counter=1
declare -i sum=0
until [ $counter -gt 100 ]; do
if [ $[$counter%2] -eq 0 ]; then
let sum+=$counter
fi
let counter++
done
echo $sum
1)break:提前退出循环;
2)break [N]: 退出N层循环;N省略时表示退出break语句所在的循环;
3)continue: 提前结束本轮循环,而直接进入下轮循环;
4)continue [N]:提前第N层的循环的本轮循环,而直接进入下轮循环;
#1.while体
while true; do
循环体
done
#2. until体
until false; do
循环体
done
标准输入:<
和<<
标准输出:>
和>>
(覆盖和追加)
标准错误输出:2>
和2>>
(覆盖和追加)
标准输出和标准错误输出,输出到不同位置:ls / > right.txt 2> error.txt
标准输出和标准错误输出,输出到同一位置:ls / > right.txt 2>&1
或 ls / &> right.txt
忽略标准输出和标准错误(即输出到/dev/null:ls / > /dev/null 2>&1
或 ls / &> /dev/null
通过键盘输入来创建文件: cat > cat2.txt
通过文件来代替键盘输入: cat > cat2.txt < existFile.txt
和常规正则比较,需要注意
支持
\w
、\d
等,但常规的\d+
时需要用\d\+
搜索
\d{2,5}
这种指定出现次数时,好括号也要转义\d\{2,5\}
用到正则的分组功能时,定义分组时小括号也要转义。如
\(\d\+\)
格式 function functionName(){command1;command2;}
,其中function可省略
函数默认返回值为0,通过$?访问,也可以返回特定值
#标准函数(无返回值,或者说默认返回值为0)
function my_func1(){
echo "无返回值函数,参数1:$1"
echo "无返回值函数,参数2:$2"
}
my_func1 "hello" "world"
echo -e "返回值是$?\n"
#省略function
my_func2(){
echo "省略function函数"
}
my_func2
echo -e "返回值是$?\n"
#有特定返回值
function sum(){
echo "有特定返回值"
local a=$1
local b=$2
return $(($a+$b))
}
sum 1 3
echo -e "返回值是$?"
定义函数99_source_function_define.sh
#仅定义函数不调用,如果在当前脚本里调用,则被其他shell脚本source时同时会触发调用
function call_by_other_shell(){
echo "我正在被其他的shell脚本调用"
}
function hello(){
echo "因为在方法定义脚本里call函数了,虽然没被调用,但被source时同样也会被执行调用"
}
hello
调用上面shell函数代码
#!/bin/bash
#调用其他shell脚本的函数前,先source。(会将source的shell文件全部先执行一遍)
source ./99_source_function_define.sh
call_by_other_shell
将要用到的函数分类定义在一个文件里,要用的脚本里直接source后就能生效
通过source引入即可
- 绝对路径:/usr/local/tomcat/start.sh param1 param2
- 相对路径:./bin/start.sh
- 命令已添加到PATH:直接start.sh运行
- bash start.sh或sh start.sh
- source执行命令(是在父进程中执行)
- 调试shell:sh -x start.sh
- sh -n start.sh仅检查是否有语法问题
- sh -v start.sh 在执行前先将script输出到屏幕上
本地变量
vname=value #作用域为整个bash进程可以使用
局部变量
local varname=value #作用域为当前代码段(一般在定义函数内使用)
环境变量
export varname=value #作用域为当前shell进程及其后代进程,不能影响到其父进程
varname=value ; export varname
特殊变量
$1,$2,$3
#shell命令的参数,从1开始
$?
#上一个命令执行状态码(0为成功)
$0
#获取当前执行的shell脚本的文件名
$#
#当前shell命令参数的总个数
$*
#所有变量
格式:declare [选项] 变量名
-r 将变量设置为只读属性
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示此脚本前定义过的所有函数名及其内容
-F 仅显示此脚本前定义过的所有函数名
-x 将变量声明为环境变量
-l 将变量值转为小写字母 declare –l var=UPPER
-u 将变量值转为大写字母 declare –u var=lower
‘${name}’ #单引号变量不会被替换
“${name}” #双引号变量会被替换
将要用到的变量全部定义在一个文件里,要用的脚本里直接source后就能生效
设置别名: alias ll="ls -lat"
, 如果要永久生效,一般放在.bash_profile里
读取指定文件里定义好的环境变量到当前进程,该文件可以不用以#!/bin/bash
开头
读取指定文件里定义好的函数到当前进程,该文件可以不用以#!/bin/bash
开头
bash是开始子进程,exit是结束子进程
不带参数,两者结果是一样的。set除了查看环境变量还查看进程内的变量
是一个特殊的内置环境变量,返回0~32767的随机整数。echo $RANDOM
如果要0-n范围内,例如: echo "$RANDOM*100/32767" |bc
特殊的环境变量,用来设置shell命令栏的格式
略
变量来自于输入流: read -p
-p prompt
: 显示提示信息,提示内容为 prompt
-t seconds
: 设置超时时间秒。指定时间内没有输入完成 将会返回一个非 0 的退出状态,读取失败。
-s
: 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的
-d delimiter
: 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)
read variable < file1.txt
: 读取文件作为变量
-a array
: 把读取的数据赋值给数组 array,从下标 0 开始。
根据表达式进行算术计算(只能算整数):echo "expression" > | bc
echo "3+5/6" | bc
history n
: 列出最近n条
history -c
: 清除history(内存中)
history -r file
: 从文件中读取history到shell中(内存),一般file为~目录下shell对应的history文件。如~/.bash_history
history -w file
: 将shell中(内存)的history写到文件中(会覆盖)
!number
:重新执行history对应number
的命令
history存储的条数由环境变量$HISTSIZE
决定,一般在/etc/profile
中被定义
执行报错的命令,也会被记录下来。用户注销时会将shell中的history刷到文件
对每行字符串进行切割,取出想要的部分
cut -d'分隔符'
:指定分隔符
cut -f 切割后的某些段
:cut -f2
取第2段,cut -f1,3,5
取某几段,cut -f1-3
取连续1段。(index从1开始)
cut -c 基于字符切割的某些字符
:cut -c2
取第2个字符,cut -c1,3,5
取1/3/5个字符,cut -f1-3
取连续1段。(index从1开始)
cut对多空个相连的数据比较不好处理
如果某行中有我们需要的信息,就把这行拿出来
grep -i
: 忽略大小写
grep -n
: 输出行号
grep -v
: 反选,输出没有命中内容的行
grep -An
: 同时输出命中行的后n行(after)
grep -Bn
: 同时输出命中行的前n行(before)
grep --color=AUTo
:可以高亮关键字,也可以设置为grep的alias
对行进行排序
-f
: 忽略大小写差异
-b
: 忽略最前面的空格符部分
-r
:反向排序
-u
:就是uniq去重,相同数据仅出现一行
-n
:按数字排序(默认按文字),一般如纯数字列可能有该需求(如122和2对比)
-t'分隔符' -kn
: 将当前行按分隔符切割后,指定切割后的第n段进行排序
参数源为一个文件或输入流
-l
: 行数(实际按$\n的个数,如果最后一行不换行可能会漏统计1行)
-w
: 字符个数(英文单字,不包含)
-m
: 多个字符
如果不带任何参数,就是输入-l -w -m
能同时将标准输出流,同时输出到屏幕和文件
-a
: 输出到文件时追加,默认覆盖
示例:last |tee last1.txt|cut -d' ' -f1
追加:cat /etc/passwd | cut -d':' -f1 |tee -a last1.txt
格式:paste file...
,将多个文件的对应行拼接到一起,第一行(file1、file2…的第一行拼接)
-d
: 指定分隔符,默认为tab
最后一个file执行为-
:用在管道后面,-
表示管道的输入
示例1:paste /etc/passwd /etc/shadow
示例2:cat productIds.txt | paste itemIds.txt 01_productName.txt - | head -3
格式:expand file
,将文件中的tab键换成8个空格
-t n
: 指定一个tab键替换成n个空格
unexpand作为正好相反,将空格替换成tab键
格式:cat file...
,依次将多个文件打印
-t
: 能看到特殊的格式,如tab为^I
利用将多个文件合成一个:cat file... >>targeFile
split [-bl] file PREFIX
:将一个文件切割成多个文件,PREFIX表示切割后文件名的前缀。
split -b 20k
:按指定大小切割
split -l file
:按指定行数切割
管道,管道前的输出作为管道后命令的输入
如果管道后的命令参数可以接受多个文件或文件参数不是在命令最后,则需显式加上输入流代参-
如ls / |split -l 5 - lsroot
和ls /etc/passwd | cat /etc/group -
将输入流的参数,以空格或换行符进行分隔得到参数列表。每个参数都执行xargs后面的命令
xargs最大的用处是,某些命令不支持管道,都通过xargs可以实现类似管道的效果
-E
:分析输入流的参数截止标记,默认为EOF
-p
:每个参数执行xargs后面的命令时都询问
-n
:每次执行xargs后面的命令时需要用到几个参数,默认为1
如果xargs后面没有命令,默认为echo每个参数
示例:cut -d':' -f1 /etc/passwd|tail -5 | xargs finger
ls subItemIda* | xargs -p -n1 cat
:每次都询问,每次都只用一个参数
cut -d':' -f1 /etc/passwd| xargs -E'lp' finger
如果每行字段精细化处理,可以用awk
语法:awk '条件类型1 {command1} 条件类型2 {command2} ...' file
执行开始前对应条件为BEGIN
,执行结束后对应条件为END
如果每行都处理,可以省略条件直接command。awk的处理流程如下:
- 读入第一行,并将第一行的数据按空格或tab分隔填入
$0
(总段数),$1
(第1段),$2
(第2段)等变量中- 依据条件类型的限制,依次判断是否需要进行后面的动作
- 做完所有的动作与条件类型
- 若还有后续的行数据,则重复1-3步骤,直到读完所有的数据
awk语句中能用到的内置变量(引用时不需要$)有:
NF:当前行的拥有的字段总数(同$0)
NR:目前awk所处理的行index(从1开始)
FS;目前行的分隔字符,默认是空格键
全局设置换行符:awd 'BEGIN {FS=":"}
格式:diff [-bBi] from-file to-file
,比较两个文件的差异
-b
:忽略仅连续空格数量的差别,如love you
和love you
的区别
-B
:忽略空白行的差别
-i
:忽略大小写的差别
获取指定格式的日期:date [+format] 支持%Y%y%m%w%d%H%M%S等变量
日期偏移:date -v[+|-]val[ymwdHMS]
%n 换行 相当于 \n
%c 单个字符
%d 十进制整数
%u 无符号十进制数
%f 十进制浮点数
%o 八进制数
%x 十六进制数
%s 字符串
%% 输出百分号
“%8.2f”: 其中8表示总共打印8个字符(不够往前充填空格),精度为小数点后2位
“%08f.2”: 其中8表示总共打印8个字符(不够往前充填0),精度为小数点后2位
“%02d”: 打印整数, 不够3三位左补零
将变量作为命令执行
express="cat tmp2.txt"
echo $express
eval "$express"
定时任务,定时调用shell
-e
:编辑定时任务
-l
:展示目前所有的定时任务
注意:
- 由于crontab是用nologin用户登陆的,所有shell里需要考虑到export变量是否生效
- 排查某个任务没执行时,可以通过重定向将执行日志放到执行文件里。command > tmp.xt 2>&1
echo -e "abc\ndef" #打印换行到控制台
echo -e "abc\ndef" > tmp.txt; #打印换行到文件