开头添加
在脚本开头添加以下行,启用严格模式,可以帮助捕获常见的错误。
set -euo pipefail
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
log_message "脚本开始执行"
set -e:任何命令返回非零值(表示错误)时,脚本会立即退出。
set -u:当引用未定义的变量时,脚本会退出并显示错误信息。
set -o pipefail:如果管道中的任何命令失败,整个管道的返回状态将设置为失败。
变量
$0 获取当前执行的shell脚本名称
$n 获取当前执行的shell脚本的第n个参数,如$1 $2
$# 获取当前执行的shell脚本的参数总个数
$* 获取当前shell脚本所有的传参参数,不加引号和$@相同,加双引号相当于“$1 $2 $3”
$@ 获取当前shell脚本所有的传参参数,不加引号和$*相同,加双引号相当于“$1”"$2""$3"
$? 获取执行上一个指令的状态,0为正确,非0为错误
$$ 获取执行当前脚本的pid
$! 获取上一个后台工作的进程的进程号
$_ 获取在此之前执行的命令或脚本的最后一个参数
变量子串
${name:0:2} 变量截取 前两个字符
${name:2:4} 变量截取 从第二个字符开始截取两位
${name} 返回变量$name的内容
${#name} 返回变量$name的内容总长度(按字符)
${name#liang} 从变量开头开始删除最短匹配liang的子串
${name##liang} 从变量开头开始删除最长匹配liang的子串(贪婪匹配)
${name%liang} 从变量结尾开始删除最短匹配liang的子串
${name%%liang} 从变量结尾开始删除最长匹配liang的子串(贪婪匹配)
${name/liang/wang} 把wang替换成第一个匹配到的liangzeyu
${name//liang/wang} 把wang替换成所有匹配到的liangzeyu(贪婪匹配)
${var}:返回${var}的内容
${#var}:返回${var}的字符长度
${var:offset}:返回${var}从位置offset之后开始提取字符至结束
${var:offset:length}:返回${var}从offset之后,提取长度为length的字符
${var#word}:返回从${var}开头开始删除最短匹配的word子符串
${var##word}:返回从${var}开头开始删除最长匹配的word子符串
${var%word}:返回从${var}结尾开始删除最短匹配的word子符串
${var%%word}:返回从${var}结尾开始删除最长匹配的word子符串
${var/oldstring/newstring}:使用newstring替换第一个匹配的字符oldstring
${var//oldstring/newstring}:使用newstring替换所有匹配的字符oldstring
${var:-word}:如果变量var的值为空或未赋值,则将word做为返回值,常用于防止变量为空或未定义而导致的异常
${var:=word}:如果变量var的值为空或未赋值,则将word赋值给var并返回其值。
${var:?word}:如果变量var的值为空或未赋值,则将word做为标准错误输出,否则则输出变量的值,常用于捕捉因变量未定义而导致的错误并退出程序
${var:+word}:如果变量var的值为空或未赋值,则什么都不做,否则word字符将替换变量的值
数字运算
+ - 加法 减号
* / % 乘 除 取模
** 幂运算
++ -- 增加 减少
! && || 逻辑符号:取反 与(and) 或(or)
< <= > >= 比较符号 小于 小于等于 大于 大于等于
== != = 相等 不相等 相当于
<< >> 向左移位 向右移位
~ | & ^ 按位取反 按位异或 按位与 按位或
= += -= *= /= %= a+=1 等于 a=a+1
运算命令
(()) 用于整数运算 ==>echo $((1+1))
let 用于整数运算 ==>let i=i+8
bc 计算器
expr 用于整数运算 ==> expr $i + 6 &>/dev/null #判断整数
$[] 用于整数运算 echo $[2*5]
awk 用于整数运算和小数运算 echo '13 14'|awk '{print $1+$2}'
条件测试
测试符 描述 示例
-e 文件或目录存在为真 [ -e path ] path 存在为 true
-f 文件存在为真 [ -f file_path ] 文件存在为 true
-d 目录存在为真 [ -d dir_path ] 目录存在为 true
-r 有读权限为真 [ -r file_path ] file_path 有读权限为 true
-w 有写权限为真 [ -w file_path ] file_path 有写权限为 true
-x 有执行权限为真 [ -x file_path ] file_path 有执行权限为 true
-s 文件存在并且大小大于0 为真 [ -s file_path ] file_path 存在并且大小大于 0 为 true
布尔运算符
运算符 描述 示例
! 非关系,条件结果取反 [ ! 1 -eq 2 ]为 true
-a 和关系,在[]表达式中使用 [ 1 -eq 1 -a 2 -eq 2 ]为 true
-o 或关系,在[]表达式中使用 [ 1 -eq 1 -o 2 -eq 1 ]为 true
逻辑判断符
&& 逻辑和,在[[]]和(())表达式中或判断表达式是否为真时使用
[[ 1 -eq 1 && 2 -eq 2 ]]为 true
(( 1 == 1 && 2 == 2 ))为 true
[ 1 -eq 1 ] && echo yes 如果&&前面表达式为 true 则执行后面的
|| 逻辑或,在[[]]和(())表达式中或判断表达式是否为真时使用
[[ 1 -eq 1 || 2 -eq 1 ]]为 true
(( 1 == 1 || 2 == 2 ))为 true
[ 1 -eq 2 ] || echo yes 如果||前面表达式为 false 则执行后面的
整数比较符
-eq 等于 [ 1 -eq 1 ]为 true
-ne 不等于 [ 1 -ne 1 ]为 false
-gt 大于 [ 2 -gt 1 ]为 true
-lt 小于 [ 2 -lt 1 ]为 false
-ge 大于或等于 [ 2 -ge 1 ]为 true
-le 小于或等于 [ 2 -le 1 ]为 false
括号用途总结
( ) 用途 1:在运算中,先计算小括号里面的内容 用途 2:数组 用途 3:匹配分组
(( ))
用途 1:表达式,不支持-eq 这类的运算符。不支持-a 和-o,支持<=、>=、<、>这类
比较符和&&、||
用途 2:C 语言风格的 for(())表达式
$( ) 执行 Shell 命令,与反撇号等效
$(( )) 用途 1:简单算数运算 用途 2:支持三目运算符 $(( 表达式?数字:数字 ))
[ ] 条件表达式,里面不支持逻辑判断符
[[ ]]
条件表达式,里面不支持-a 和-o,不支持<=和>=比较符,支持-eq、<、>这类比较
符。支持=~模式匹配,也可以不用双引号也不会影响原意,比[]更加通用
$[ ] 简单算数运算
{ }
对逗号(,)和点点(...)起作用,比如 touch {1,2}创建 1 和 2 文件,touch
{1..3}创建 1、2 和 3 文件
${ }
用途 1:引用变量
用途 2:字符串处理
其他
* [:digit:]:所有数字,即[0-9]
* [:lower:]:所有小写字母
* [:upper:]:所有大写字母
* [:alpha:]:所有字母
* [:alnum:]:0-9,a-z,A-Z
* [:space:]:空白字符
* [:punct:]:所有标点符号
shell语法大全
if语句
单行/多行注释
#--------------------------------------------
# shell 注释示例
# author:zp
#--------------------------------------------
# echo '这是单行注释'
########## 这是分割线 ##########
:<<EOF
echo '这是多行注释'
echo '这是多行注释'
echo '这是多行注释'
EOF
单分支
if (command);then
fi
if [ 条件表达式 ];then
条件成立要执行的代码
fi
if [ 条件表达式 ]
then
条件成立要执行的代码
fi
双分支
格式1: if test 条件表达式;then
fi
格式2: if [ 条件表达式 ];then
fi
格式3: if [[ 条件表达式 ]];then
fi
if [ 条件表达式 ];then
条件成立要执行的代码
else
条件不成立要执行的代码
fi
if [ 条件表达式 ]
then
条件成立要执行的代码
else
条件不成立要执行的代码
fi
多分支
if [ 条件表达式 ];then
条件成立要执行的代码
elif [ 条件表达式 ];then
条件成立要执行的代码
else
条件不成立要执行的代码
fi
if [ 条件表达式 ]
then
条件成立要执行的代码
elif [ 条件表达式 ];then
条件成立要执行的代码
else
条件不成立要执行的代码
fi
for循环
for 变量 in 取值列表[``|$()]
do
循环体代码
done
for 变量 in 取值列表[``|$()] ;do
循环体代码
done
while 循环
while [条件表达式];do
循环体内容
done
while true
do
循环体内容
done
while : ;do
循环体内容
done
case语句
在shell中case语句一般格式如下:
case "变量值" in
值1)
指令 ...
;;
值2)
指令...
;;
*)
指令 ...
;;
esac
case实例:
val=$1
case ${val} in
1)
echo "1"
;;
2)
echo "2"
;;
*)
echo "$val for uncertain"
;;
esac
funcation 函数
function name() {
statements
[return value]
}
name() {
statements
[return value]
}
function name {
statements
[return value]
}
funcation_name () {
statements
[return value]
}
其他
[条件表达式] && {
代码内容
}
[条件表达式] || {
}
整数测试
常见整数运算符
| 运算符 | 说明 |
|---|---|
| number1 -eq number2 | 比较两个是否相等,如果相等正确输出 |
| number1 -ne number2 | 两个是否不等,不等则正确输出 |
| number1 -gt number2 | number1是否大于number2,是则正确输出 |
| number1 -lt number2 | number1是否小于number2,是则正确输出 |
| number1 -ge number2 | 是否大于等于....... |
| number1 -le number2 | 是否小于等于....... |
字符串测试
字符串测试运算符
| 运算符 | 说明 |
|---|---|
| string | 判断指定的字符串是否为空 |
| string1=string2 | 判断两个字符串是否相等 |
| string1!=string2 | 判断两个字符串是否不等 |
| -n string | 判断string是否是非空字符串 |
| -z string | 判断string是否是空字符串 |
文件测试
常见文件操作符
| 操作符 | 说明 |
|---|---|
| -a | 判断文件是否存在。 存在输出0 |
| -b | 文件是否存在且为块文件。 |
| -c | 文件是否存在且为字符文件 |
| -d | 文件是否存在且为目录 |
| -e | 与-a相同 |
| -s | 文件非空 |
| -f | 文件存在且是常规文件 |
| -w | 文件存在且可写 |
| -L | 文件存在且为符号链接 |
| -u | 文件是否有suid位 |
| -r | 文件存在且可读 |
| -x | 文件存在且可执行 |
判断命令结果
# 写法一
command || { echo "command failed"; exit 1; }
# 写法二
if ! command; then echo "command failed"; exit 1; fi
# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
lsb_functions="/lib/lsb/init-functions"
if test -f $lsb_functions ; then
. $lsb_functions
else
log_success_msg()
{
echo " SUCCESS! $@"
}
log_failure_msg()
{
echo " ERROR! $@"
}
fi
shell实现增加数字编号
要在Shell脚本中通过增加数字来实现变量后面的01、02、03、04等形式,可以使用循环和printf格式化函数。以下是一个示例代码:
shell
#!/bin/bash
base_name="variable" # 变量名的基础部分
start_number=1 # 起始数字
end_number=4 # 结束数字
for ((i=start_number; i<=end_number; i++)); do
# 使用printf格式化函数将数字转换为两位数的字符串
padded_number=$(printf "%02d" $i)
variable_name="${base_name}${padded_number}"
echo "$variable_name"
done
在上述代码中,我们将base_name设置为变量名的基础部分,start_number和end_number分别设置为起始数字和结束数字。
然后,使用for循环从起始数字遍历到结束数字。在循环中,我们使用printf格式化函数将循环变量i转换为两位数的字符串,并将其附加到base_name后面,形成最终的变量名。
最后,我们通过echo语句输出每个生成的变量名。
变量扩展
语法 说明
${parameter:-defaultValue} 获取默认 shell 变量值
${parameter:=defaultValue} 设置默认 shell 变量值
${parameter:?"Error Message"} 如果未设置参数,则显示错误消息
${#var} 查找字符串的长度
${var%pattern} 从最短的后端(末端)模式中移除
${var%%pattern} 从最长的后端(末端)图案中移除
${var:num1:num2} 子串
${var#pattern} 从最短的正面图案中移除
${var##pattern} 从最长的正面图案中移除
${var/pattern/string} 查找和替换(仅替换第一次出现)
${var//pattern/string} 查找并替换所有匹配项
${!prefix*} 扩展到名称以前缀开头的变量的名称
${var,} 将第一个字符转换为小写。
${var,pattern} 如果匹配成功,才将第一个字符转换为小写。
${var,,} 将所有字符转换为小写。
${var,,pattern} 如果匹配成功,才将所有字符转换为小写。
${var^} 将第一个字符转换为大写。
${var^pattern} 如果匹配成功,才将第一个字符转换为大写。
${var^^} 将所有字符转换为大写。
${var^^pattern} 如果匹配成功,才将所有字符转换为大写。
cat 使用技巧
#变量不会被解析
cat << 'EOF' > test.txt
1 $USER
2
3
EOF
#变量自动解析为值
cat << EOF > test.txt
1 $USER
2
3
EOF
#shell脚本输出
cat << EOF
Hello, world!
Welcome to my script.
EOF
#shell脚本输出
cat <<-EOF
Hello, world!
Welcome to my script.
EOF
cat > /root/test-chart/templates/configmap.yaml << EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap-test
data:
config.yaml: |
{{ .Files.Get "redis.properties" | indent 4 }} #引用chart根目录下的redis.properties文件内容
EOF
cat > /root/test-chart/redis.properties << 'EOF'
redis.host=127.0.0.1
redis.port=6379
redis.passwd=123456
EOF
cat <<EOF | tee /etc/network.conf
redis.host=127.0.0.1
redis.port=6379
redis.passwd=123456
EOF
输出
function format () {
echo -e "\033[32m $* \033[0m"
}
记录时间
#!/usr/bin/env bash
starttime=$(date +'%Y-%m-%d %H:%M:%S')
# .........命令体
endtime=$(date +'%Y-%m-%d %H:%M:%S')
start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);
echo "${log_path} 目录下的日志已排查完成,共耗时"$((end_seconds-start_seconds))"s" > /tmp/log_delete.log
getopts 用法
[root@mysql nginx]# vim test.sh #脚本内容如下
#!/usr/bin/env bash
while getopts ":h:p:" optname;do
case "$optname" in
"h")
host_ip=$OPTARG
;;
"p")
host_port=$OPTARG
;;
"?" )
echo "不知道此选项"
;;
esac
done
echo "IP是${host_ip},端口是${host_port}"
#执行效果如下
[root@mysql nginx]# sh test.sh -h 192.168.20.2 -p 3306
IP是192.168.20.2,端口是3306
[root@mysql nginx]# sh test.sh -p 22 -h 192.168.20.3
IP是192.168.20.3,端口是22
shell数组
#!/bin/bash
# 声明并初始化数组
declare -a fruits=("apple" "banana" "cherry")
# 添加一个元素到数组
fruits+=("date")
# 打印数组中的所有元素
echo "${fruits[@]}"
# 获取数组的长度
echo "The length of the array is ${#fruits[@]}"
# 访问数组的第一个元素
echo "The first fruit is ${fruits[0]}"
# 遍历数组中的所有元素
for fruit in ${fruits[@]}; do
echo "${fruit}"
done
# 使用花括号展开数组
for ((i=0; i<${#fruits[@]}; i++)); do
echo "${fruits[$i]}"
done
#!/usr/bin/bash
failedArray=()
nameArray=()
while IFS=' ' read -r name ip; do
echo "name: $name"
echo "ipaddress: $ip"
failedArray+=("$ip is successfully")
nameArray+=("$name is successfully")
done<ip.txt
echo "ip num is ${#failedArray[@]}"
for item in ${failedArray[@]}; do
echo $item
done
for item in ${!failedArray[@]}; do
echo ${failedArray[$item]}
done
echo ${failedArray[@]}
echo ${nameArray[@]}
解析命令行参数
#!/usr/bin/bash
basedir=""
datadir=""
TEMP=`getopt -o "" --long basedir:,datadir: -- "$@"`
eval set -- "$TEMP"
while true;do
case $1 in
--basedir)
basedir=$2
shift 2
;;
--datadir)
datadir=$2
shift 2
;;
--)
shift
break
;;
*)
echo "Usage ...."
exit -1
esac
done
echo "basedir $basedir"
echo "datadir $datadir"
参数
PARSED=$(getopt --options=b:d: --longoptions=basedir:,datadir: --name "$0" -- "$@")
eval set -- "$PARSED"
if [ $? -ne 0 ];then
echo "Terminting...."
exit
fi
while true; do
case "$1" in
-b|--basedir)
basedir=$2
shift 2
;;
-d|--datadir)
datadir=$2
shift 2
;;
--)
shift
break
;;
*)
break
;;
esac
done
echo $basedir
echo $datadir
#!/bin/bash
# 定义变量来存储选项值
file=""
verbose=0
# 定义选项字符串
options="f:vh"
# 使用 getopts 解析命令行参数
while getopts "$options" opt; do
case $opt in
f | --file)
file=$OPTARG
;;
v | --verbose)
verbose=1
;;
h | --help)
echo "Usage: $0 [-f|--file <file>] [-v|--verbose] [-h|--help]"
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
# 处理选项后的剩余参数
shift $((OPTIND-1))
# 输出解析结果
echo "File: $file"
if [ $verbose -eq 1 ]; then
echo "Verbose mode enabled."
fi