SHELL脚本语法示例

shell脚本在linux使用用的十分广泛,写篇总结学习性的文章做记录。

这个运维手册写的比较详细,可以做参考。

另一个比较全的参考

基本语法

  1. if else:
    # then 与 if 同一行时要加 ;
    if [ `whoami` != "root" ];then
        echo "Please run it as a superuser"
        exit 0
    elif $something -gt 2 ; then
        echo "else if"
    else
        echo "else"
    fi
    
  2. switch:
    read -p $'Please input a number...    \n1 means icmp,2 means udp ,3 means tcp '
    case $n in 
    1)
        echo "you will accept icmp data"
        tcpdump icmp -c 5 -i ens33 -l -w./aaa.cap &
        ;;
    2)
        echo "you will accept udp data"
        tcpdump udp -c 5 -i ens33
        ;;
    3)
        echo "you will accept tcp data"
        tcpdump tcp -c 5 -i ens33 
        ;;
    *)
        echo "invail command"
        ;;
    esac
    
  3. for:
    datetime=`date +%F-%H:%M:%S`
    i=1
    while [ $i -lt 5 ];do
        echo "start NO.$i capture data"
        # sudo tcpdump icmp -i ens33 -w ./$datetime.cap &
        # sudo tcpdump icmp -i ens33 -w ./`date +%F-%H:%M:%S` &
        tdid=`pgrep tcpdump`
        sleep 10s
        echo "$tdid"
        kill -9 $tdid
        ((i++))
    done


    for i in ${!IdArry[@]}
    do
        echo ${IdArry[i]:1}
        # echo ${IdArry[i]:i:j} 表示打印第i位到第j位数据,j省略表示第i位到最后一位
    done

关系运算符,如下:

注意: 使用 [] 就可以使用c中常用的关系符, # [ ]符号旁必须有空格,否则会被shell认为是命令执行

  1. -gt:大于,greater than。
  2. -eq:等于,equal。
  3. -lt:小于,less than。
  4. -ge:大于等于,greater than or equal。
  5. -le:小于等于,less than or equal。
  6. -ne:不等于,not equal。

连接符,如下:

  1. -a:且,and。
  2. -o:或,or。

条件判断,逻辑运算符,如下:

  1. &&:用来执行条件成立后执行的命令。
  2. ||:用来执行条件不成立后的执行命令。

函数、传参、返回值

给定的参数以$1,$2,$3,...$n的形式访问,对应于函数名后参数的位置。$0变量的值是函数的名称。
$? 表示上次运行的结果,非0表示异常
$# 变量用于保存赋予函数的位置自变量/参数的数量。其中 $* 和 $@ 变量用于保存赋予函数的所有参数。

传参时,若$cmd中带空格需要加"" , 不加双引号会自动以空格为分割符号传参
数组传参数使用 ${Mymap[*]} 或 ${Mymap[@]},区别为${Mymap[*]} 是传入一个参数,
例如“1 2 3”${Mymap[@]} 是传入多个参数,例如"1","2","3"
# 声明函数的语法有两种格式定义:
# 第一种方法:以函数名称开头,后跟括号。这是最优选且最常用的方法,语法如下:
function_name () {  
   commands  
}
# 单行语法如下:
function_name () { commands; }

# 第二种方法:以函数保留字开头,后跟函数名称:
function function_name {  
    commands  
}
# 单行语法如下:
function_name () { commands; }

数组、关联数组(字典)变量

索引-1是最后一个元素的参考

declare -a ARRAY_NA 声明数组 declare -A ARRAY_NAME 声明关联数组 local temp 声明局部变量

注意脚本中哪怕函数内定义变量默认是全局的,在函数内定义的local变量会结束作用域后销毁。

关联数组用法示例:

# 访问元素类似数组
Mymap[${NameArry}]

# 添加元素
Mymap[${NameArry}] = 12

# 删除元素
unset Mymap[$findkey] 删除操作

# 遍历元素
for i in ${IdArry[@]}
do
    echo i
done

# ${!Mymap[@]}为数组或字典全部index   ${Mymap[@]}为全部value
#

&() 与 ``区别

在操作上,这两者都是达到相应的效果。在bash中,$( )与``(反引号,博客格式有问题,这里打中文的代替)都是用来作命令替换的。命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。

$ echo today is $(date "+%Y-%m-%d")
today is 2021-08-01

在多层次的复合替换中,``必须要额外的跳脱处理(反斜线),而$( )比较直观。 最后,$( )的弊端是,并不是所有的类unix系统都支持这种方式,但反引号是肯定支持的。

# 将cmd1执行结果作为cmd2参数,再将cmd2结果作为cmd3的参数
cmd3 $(cmd2 $(cmd1))
# 如果是用反引号,直接引用是不行的,还需要作跳脱处理
cmd3 `cmd2 \`cmd1\``

trap

详细trap介绍

在shell中,使用内置命令trap(中文就翻译为陷阱、圈套)也可以布置所谓的陷阱,这个陷阱当然不是捕老鼠的,而是捕捉信号。

通常trap都在脚本中使用,主要有2种功能:

(1).忽略信号。当运行中的脚本进程接收到某信号时(例如误按了CTRL+C),可以将其忽略,免得脚本执行到一半就被终止 (2).捕捉到信号后做相应处理。主要是清理一些脚本创建的临时文件,然后退出

进程结束临时文件销毁示例

# XXX会被随即字符代替保证唯一,-d生成目录,-t表示生成在/temp中,
# 脚本临时文件全部存在此处,程序捕获到EXIT后执行finish删除临时文件夹
scratch=$(mktemp -d -t coretmp.XXX)
function finish {
  rm -rf "$scratch"
}
trap finish EXIT

调用core api示例

awk grep sed 是处理文本的三大利器,后面示例用到了用到了awk,直接将其返回一个变量会传输到数组中。

#!/bin/bash
scratch=$(mktemp -d -t coretmp.XXX)
function finish {
  rm -rf "$scratch"
}
trap finish EXIT

function ApiCall {
  local nodeId=$1; 
  local cmd=$2;
  # tee一边重定向到文件一边打印,防止等待response卡死,也方便打印报错信息 
  coresendmsg execute flags=tty node=$nodeId number=1001 command="$cmd" -l | tee $scratch/core_msg
  res=$(awk -F ': ' '{if($1 ~ /RESULT/) print $2}' $scratch/core_msg)
  if [ "$res" == "" ]; then
    exit 0
  else 
      echo -e "excute core api command: \n$res\n"
  fi
  eval $res
}

function SetMap {
  file=$1
  Mymap=$2
  local IdArry=(`awk '$1=="node" {print $2}' $file`)
  local NameArry=(`awk '$1=="hostname" {print $2}' $file`)

  for i in ${!IdArry[@]}
  do
    Mymap[${NameArry[i]}]=${IdArry[i]:1}
  done
  # echo ${!Mymap[@]} ${Mymap[@]}
}

# 默认命令、场景文件参数
nodeId='1'
protocol='icmp'
interface='eth0'
imnPwd=$(echo $HOME/.core/configs/)
scene='sample1'
declare -A Mymap

# 读取参数
if [ $# == 0 ]; then
  echo -e "The required parameters are imn scenario name and node name\n\
When \$3 is CMD, \$4 is the command to execute...
Otherwise, \$3 indicates the NIC ID and \$4 indicates the packet capture protocol (optional).\n\
!! default: nodename=n1, interface=$interface, protocol=$protocol(options) !!
sense file: echo $imnPwd$scene.imn"
else
  scene=$1
  file=$(echo $imnPwd$scene.imn)
  echo -e "sense file: $file"
  SetMap $file ${Mymap[*]}  
  # echo "场景节点名: ${!Mymap[@]} \n 场景节点id: ${Mymap[@]}"
  nodeId=${Mymap[$2]}
  interface=$3
  protocol=$4
  if [ ! -n "$nodeId" ]; then
    echo "node name error!"
    exit 0
  fi
fi

if [ "$3" == "cmd" ] ; then
  ApiCall $nodeId "$4"
elif [ $# -ge 3 ] || [ $# == 0 ];  then
  cmd=$(echo tcpdump $protocol -i $interface -l)
  ApiCall $nodeId "$cmd"
else 
  echo "Invalid input parameter"
  exit 0
fi