SHELL 脚本

shell script 缺点:速度较慢,且使用的 CPU 资源较多,造成主机资源分配不良。

一个简单的 shell 脚本

模板

[why@yingzai shell]$cat sample.sh
#!/bin/bash
# 脚本名称: sample.sh
# 功能描述: 这是一个示例脚本,演示基本结构和常用功能
# 创建日期: 2025-03-21
# 作者: Your Name

# --------------------------
# 初始化配置
# --------------------------
SCRIPT_NAME=$(basename "$0")  # 获取脚本名称
LOG_FILE="/tmp/${SCRIPT_NAME%.*}.log"  # 生成日志文件名
TIMESTAMP=$(date +"%F %T")    # 时间戳格式

# --------------------------
# 函数定义
# --------------------------
# 日志记录函数
log() {
  echo "[${TIMESTAMP}] $1" | tee -a "$LOG_FILE"
}

# 错误处理函数
error_exit() {
  log "错误: $1"
  exit 1
}

# 帮助信息
usage() {
  cat << EOF
用法: 
  $SCRIPT_NAME [选项] <参数>

选项:
  -h, --help    显示帮助信息
  -v, --version 显示版本信息

示例:
  $SCRIPT_NAME input.txt
EOF
}

# --------------------------
# 主程序逻辑
# --------------------------
main() {
  # 检查参数
  if [[ $# -eq 0 ]]; then
    usage
    error_exit "未提供必要参数"
  fi

  # 处理输入文件
  local input_file=$1
  if [[ ! -f "$input_file" ]]; then
    error_exit "文件不存在: $input_file"
  fi

  log "开始处理文件: $input_file"
  
  # 示例处理流程
  grep "error" "$input_file" /tmp/errors.txt && {
    log "找到 $(wc -l < /tmp/errors.txt) 个错误"
  } || {
    log "未发现错误"
  }

  log "处理完成"
}

# --------------------------
# 脚本入口
# --------------------------
# 解析参数
while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    -v|--version)
      echo "版本 1.0"
      exit 0
      ;;
    *)
      main "$1"
      shift
      ;;
  esac
done

说明

  • #!/bin/bash:解释器,告诉系统,该程序需要什么 shell 来执行

  • 注释:#

  • 环境变量的声明:PATH 和 LANG 是重要的环境变量

    为了方便在脚本中执行外部命令,而不必写绝对路径,可以在脚本开头声明。

  • 权限:shell 脚本执行需要 x 权限, chmod a+x filename

  • 执行结果:可以自定义错误信息,将错误编码返回给系统 exit n

执行

执行方式

  • bash xxx.sh or sh xxx.sh or ./xxx.sh

    • 在子进程中运行,不会影响父进程的环境变量
  • source xxx.sh or . xxx.sh (. 是 source 的简写)

    • 在父进程中执行,影响父进程的环境变量

    • 最典型的例子:source /etc/bashrc

注意事项

  • 命令执行是从上而下、从左而右

  • 命令和参数间的多个空白会被忽略掉。tab 所得的空白会被视为空格。

  • 空白行也会被忽略

  • 读到 <Enter符号,就开始执行命令;换行可以使用 <Enter>

编写

与用户交互

  • 语法

    read -p "提示语" 变量名
    
  • 示例

    #!/bin/bash
    
    read -p "Please input you first name: " firstname
    read -p "Please input you last name: " lastname
    echo -e "\n you name: $firstname $lastname"
    

获取命令的执行结果

  • 语法

    变量名=$(命令)
    or
    变量名=`命令`
    
  • 示例

    #!/bin/bash
    
    date1=$(date +"%F %T")
    echo -e "now: $date1"
    

计算式

  • 语法

    变量=$(( 计算式))
    
  • 示例

    #!/bin/bash
    
    read -p "input you first number: " no_one
    read -p "input you second number: " no_second
    total=$(($no_one+$no_second))
    echo -e "\ntotal: $total"
    

判断式

  • test 命令

    • 文件类型
      image-1765505179108

    • 文件权限
      image-1765505187616

    • 文件比较
      image-1765505198885

    • 整数比较
      image-1765505207639

    • 字符串比较
      image-1765505218325

    • 多充条件判定
      image-1765505225561

  • 判断符号 [ ]

    • 注意

      • 中括号中的每个组件都需要用空格来分隔

      • 中括号内的变量,最好以双引号扩起来

      • 中括号内的常量,最好以单引号或双引号扩起来

    • 示例

      [why@yingzai shell]$name="ying zai"
      [why@yingzai shell]$[ $name == "ying" ]
      -bash: [: too many arguments
      [why@yingzai shell]$[ "$name" == "ying" ]
      

变量

  • 默认变量

    • $0:脚本名

    • $1:第 1 个传入参数

    • $2:第 2 个传入参数

    • $n:第 n 个传入参数

    • $#:传入的参数个数

    • $@:表示所有参数

    • $*:表示 “$1c$2c$3c$4”,其中 c 为分隔字符,默认是空格。

      不加双引号时,和 $@ 相同。加了双引号,会把参数视为一个参数 $1$2$3$4

    • $?:获取上一条命令的返回值。0 表示成功。

    • :获取脚本的 pid,把进程写进 pid 文件,方便管理。 ```shell echo $$ /tmp/nginx.pid ```

    • $_:获取命令最后一个参数

  • 偏移变量:shift

    • 示例

      #!/bin/bash
      
      echo "file name: $0"
      echo "params number: $#"
      echo "params: $@"
      
      shift
      echo "params: $@"
      shift
      echo "params: $@"
      shift
      echo "params: $@"
      

      结果

      [why@yingzai shell]$sh shift.sh 1 2 3 4 5 6
      file name: shift.sh
      params number: 6
      params: 1 2 3 4 5 6
      params: 2 3 4 5 6
      params: 3 4 5 6
      params: 4 5 6
      

条件判断式

  • if 判断

    • if … then

      if [ "$yn" == "YES" ] || [ "$yn" == "yes" ]; then
          xxx
      fi
      
    • if … then … elif … then

      if [ "$yn" == "YES" ] || [ "$yn" == "yes" ]; then
          xxx
      elif [ "$yn" == "NO" ] || [ "$yn" == "no" ]; then
          xxx
      fi
      
    • if … then … elif … then … else

      if [ "$yn" == "YES" ] || [ "$yn" == "yes" ]; then
          xxx
      elif [ "$yn" == "NO" ] || [ "$yn" == "no" ]; then
          xxx
      else
          xxx
      fi
      
  • case 判断

    • case … esac

      #!/bin/bash
      
      read -p "Please input a name: " name
      
      case $name in
              "jim")
                      echo "Hi, jim"
                      ;;
              "tom")
                      echo "Hello, tom"
                      ;;
              *)
                      echo "Hello, $name"
                      ;;
      esac
      

循环

  • 不定循环

    • while:满足条件,进入循环

      while [ 条件 ]
      do
      	xxx
      done
      
    • until:满足条件,终止循环

      until [ 条件 ]
      do
      	xxx
      done
      
  • 固定循环

    • for var in … do … done

      #!/bin/bash
      
      for nu in $(seq 1 5)
      do
              echo -e "$nu"
      done
      
    • for (( 初始值; 限制值; 步长 )) … do … done

      #!/bin/bash
      
      read -p "Pleae input a number: " nu
      
      for (( i = 0; i < $nu; i++ ))
      do
              echo -e "i = $i"
      done
      
  • 内置命令

    • exit:结束

    • break:跳出循环

    • continue:继续下次循环

    • return:返回一个值

  • 扩展

    • 指定分隔符

      for 循环中默认以空格作为分隔符,所以获取变量时可能会导致错误。
      解决方法:在 shell 脚本中指定 IFS 分隔符。

      [root@yingzai scripts]# vim test.sh 
      #!/bin/bash
      a="12 3 4"
      b="5 6"
      c=($a $b)
      
      IFS=$''
      
      #for i in ${c[@]}
      for i in {$a,$b}
      do
        echo $i
      done
      
    • while 按行读取文件

      #!/bin/bash
      
      # 写到标准输出
      cat << EOF
              target: read lines from another file.
              auther: yingzai
      EOF
      
      while read line
      do
              col1=`echo $line|awk '{print $1}'`
              col2=`echo $line|awk '{print $2}'`
              echo -e "$col1 + $col2"
      done < error.log
      
    • 静默设置密码

      [root@yingzai ~]#useradd user1
      [root@yingzai ~]#echo "12345678" | passwd --stdin user1
      Changing password for user user1.
      passwd: all authentication tokens updated successfully.
      

函数

  • 语法

    # 函数名后面有无括号都对
    # 函数要写在主程序的前面,否则会调用失败,这是 shell script 的执行顺序导致的。
    function 函数名 {
    	xxx
    }
    

追踪和调试

选项

sh -nvx xxx.sh
-n: 不执行脚本,仅查询语法
-v: 在执行前,输出脚本内容
-x: 输出执行的脚本内容

Windows 和 Unix 断行符问题

Windows 断行符和 Unix 断行符替换,可以使用工具 dos2unix 和 unix2dos2。

dnf install dos2unix -y
dos2unix filename