shell核心基础知识 进阶版

开头添加

在脚本开头添加以下行,启用严格模式,可以帮助捕获常见的错误。

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容