shell 脚本

在 shell 脚本中调用另一个脚本

  • fork:直接调用script_path/filename.sh(有可执行权限)或者sh script_path/filename.sh(没有可执行权限)
    • 运行时终端会新开一个子 shell 执行脚本,子 shell 执行的时候,父 shell 仍在。子 shell 执行完毕返回父 shell,但是父 shell 不能继承子 shell 的环境变量。
  • exec:exec script_path/filename.sh
    • exec 不需要新开一个子 shell 来执行被调用的脚本,而是在同一个 shell 执行。但是父脚本中exec行之后的内容不会被执行。
  • source:source script_path/filename.sh

    • source 不需要新开一个子 shell 来执行被调用的脚本,而是在同一个 shell 执行。即父脚本可以获取和使用子脚本中声明的变量和环境变量。

      #!/bin/bash
      A=1
      
      echo "before exec/source/fork: PID for parent.sh = $$"
      
      export A
      echo "In parent.sh: var A=$A"
      
      case $1 in
      --exec)
          echo -e "==> using exec..\n"
          exec ./child.sh
          ;;
      --source)
          echo -e "==> using source...\n"
          source ./child.sh
          ;;
      *)
          echo -e "==> using fork by default...\n"
          ./child.sh
          ;;
      esac
      
      echo "after exec/source/fork: PID for parent.sh = $$"
      echo -e "In parent.sh: var A=$A"
      
      #!/bin/bash
      
      echo "PID for child.sh = $$"
      echo "In child.sh: get var A=$A from parent.sh"
      
      A=2
      export A
      echo -e "In child.sh: var A=$A\n"
      

shell 命令行选项解析

  • getopts是 shell 内建命令,getopt是一个独立外部工具
  • getopts语法简单,getopt语法较复杂
  • getopts不支持长参数(如 -dir, –dir),getopt支持
  • getopts不会重排所有参数顺序,getopt会重排参数顺序
  • getopts是为了代替getopt较快捷的执行参数分析工作

getopts

  • 语法getopts optstring name [args]

    • optstring代表可使用的选项列表,每个字母代码一个选项
    • :意味着除了定义之外,还会带一个参数作为选项的值
    • 不带:是一个开关型选项,不需要指定参数的值
    • 命令行中包含了没有在getopts列表中的选项会有警告,在optstring前加上:可以消除警告
    • getopts中有个相对固定的常量OPTARGOPTIND
    • OPTARG代表当前选项的值
    • OPTIND代表当前选项在参数列表中的位置
    • 出现不认识的选项参数为?case中最后一个?用于处理这种情况,因此选项中不应包含?,
    • getopts解析完参数后,可以使用shift把选项参数进行移位操作,左边的参数就丢失了
    • shift $(($OPTIND-1)),参数从 1 开始编号,处理一个开关型选项OPTIND加 1,处理一个带值的选项参数OPTIND加 2
    • 选项参数的格式是-d val,中间需要一个空格
    • 选项参数必须放在其他参数前面,因为遇到非-开头的参数或者选项参数结束标记--就终止了
    • 中间遇到非选项的命令行参数,后面的选项参数取不到

      #!/bin/bash
      
      # echo "usage: ./`basename $0` [-d dir_name] [-f file_name] [-c commit_id] [-s service_name] [-N]"
      # echo "initial index $OPTIND"
      while getopts 'd:f:c:s:N' opt;
      do
      case ${opt} in
          d)
              echo "directory name: $OPTARG"
              ;;
          f)
              echo "file name: $OPTARG"
              ;;
          c)
              echo "commit id: $OPTARG"
              ;;
          s)
              echo "service name: $OPTARG"
              ;;
          N)
              echo "use new rep url"
              ;;
          ?)
              echo "usage: ./`basename $0` [-d dir_name] [-f file_name] [-c commit_id] [-s service_name] [-N]"
              exit 1;
              ;;
      esac
      echo "opt is $opt, arg is $OPTARG, index is $OPTIND"
      done
      
      echo "After process arguments: OPTIND=$OPTIND"
      echo "Remove processed arguments: number=$(($OPTIND-1))"
      shift $(($OPTIND-1))
      echo "Arguments index: OPTIND=$OPTIND"
      echo "Remaing arguments: $@"
      

getopt

  • 语法支持三种
    • getopt optstring parameters
    • getopt [options] [--] optstring parameters
    • getopt [options] -o|--options optstring [options] [--] parameters
    • -o表示短选项,两个冒号表示该选项有一个可选参数,可选参数必须紧贴选项,中间没有空格
    • -l|--longoptions表示长选项
    • "$@"将命令行参数展开分别的单词
    • -n出错时打印的信息

shell 获取脚本的进程 ID

  • 参数$$获取进程 ID
  • 参数$PID获取父 shell 的进程 ID
  • 参数$UID获取执行当前的当前用户 ID

shell 脚本获取当前时间

  • 获取当前时间date +%Y%m%ddate +%Fdate +%y%m%d
    • Y 年,如 2018
    • y 年的最后两位,如 2018 显示 18
    • m 月(01..12)
    • d 每个月第几天(01..31)
    • F 完整的日志(%Y-%m-%d)
  • 输出另外一个时区的时间env TZ=timezone dateenv TZ=timezone date +%Y%m%d

    • timezone 是指定的时区,比如America/Los_AngelesAsia/Shanghai

      starttime=`date +%s`
      sleep 10 #sleep 10 sec
      endtime=`date +%s`
      difftime=$(( endtime - starttime ))
      

shell 执行多个命令的方法

  • |:命令之间用|隔开,将前一个命令的标准输出(stdout)作为下一个命令的标准输入
    • 标准错误(stderr)仍在默认的地方
  • |&:命令之间用|&隔开,将前一个命令的标准输出和标准错误都作为下一个命令的标准输入
    • bash 版本不小于 4
  • ;:命令之间用;隔开,各命令执行结果不影响,即各个命令都会执行
  • &&:命令之间用&&隔开,只有前面的命令执行成功,才会执行后面的命令,保证执行过程都是成功的
  • ||:命令之间用||隔开,只有前面的命令执行失败,才会执行后面的命令,直到执行成功

    • 可用于捕获子 shell 的错误码,比如output="$( (cd "$FOLDER"; eval "$@") 2>&1 )" || errcode="$?"
    • 重要的命令失败时退出自定义错误码,比如output="$( (cd "$FOLDER"; eval "$@") 2>&1 )" || exit 12
    • 可以定义函数在||之后调用

      handle_error() { #do staff }
      output="$( (cd "$FOLDER"; eval "$@") 2>&1 )" || handle_error
      

shell test 命令

  • test 命令用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试

数值测试

  • 运算符:-eq,-ne,-gt,-ge,-lt,-le

    n1=1
    n2=2
    test $[n1] -eq $[n2] && echo '两个数相等!'
    test $[n1] -eq $[n2] || echo '两个数不相等!'
    
  • []内执行基本的算术运算

    n1=1
    n2=2
    n3=$[n1+n2]
    

字符串测试

  • 运算符: =,!=, -z(字符串长度是否为0), -n(字符串长度是否不为0)

    s1="s1"
    s2="s2"
    test $s1 = $s2 && echo '两个字符串相等!'
    test $s1 = $s2 || echo '两个字符串不相等!'
    

文件测试

  • 运算符:-e,-r,-w,-x,-s,-d,-f,-c,-b

    • -r/w/x:如果文件存在且可读/可写/可执行
    • -s:如果文件存在且至少有一个字符
    • -f/c/b:如果文件存在且是普通文件/字符型特殊文件/块特殊文件

      test -e filename && echo "文件已存在!"
      test -e filename || echo "文件不存在!"
      

连接测试条件

  • 可用逻辑操作符将测试条件连接起来:与(-a),huo(-o),非(!)

    test -e filename -o -e anotherfile && echo "至少存在一个文件!"
    test -e filename -o -e anotherfile || echo "两个文件都不存在!"
    

shell 变量

  • 定义变量是变量名和等号之间不能有空格
  • 使用时在前面加上$即可
  • 删除变量:unset $VAR
  • 设置变量只读:readonly $VAR
  • 测试变量是否定义

    • if (set -u; : $VAR) ; then echo "set" ; else echo "unset" ; fi
    • set -u用于设置 shell 选项,u 是 nounset, 表示当使用未定义的变量时,输出错误并强制退出
    • : 是不做任何事只是展开参数,单不会试图去执行
    • 没有:则将变量解释成 shell 命令,并试图去执行
    • 使用-z-n判断
    • -z:字符串长度是否为0
    • -n:字符串长度是否不为0

      echo "********************************set KK"
      export KK="kiki"
      echo KK=$KK
      if [ -z $KK ] ; then echo "unset" ; else echo "set" ; fi
      if ( set -u; : $KK ) ; then echo "set" ; else echo "unset" ; fi
      echo KK=$KK
      
      echo "********************************unset KK"
      unset KK
      echo KK=$KK
      if [ -z $KK ] ; then echo "unset" ; else echo "set" ; fi
      if ( set -u; : $KK ) ; then echo "set" ; else echo "unset" ; fi
      echo KK=$KK
      

shell 脚本上传 ftp

上传单个文件脚本

#!/bin/bash
FILENAME=$1
ftp -n -p<<!
## ftp server ip
open ftpIp
## ftp username and password
user ftpUser ftpPwd
## transfer type
binary
## upload path
cd /VDMSSoftware/cppci
## interactive mode
prompt
## upload filename
put $FILENAME
close
## close connection
bye
!
  • 传输文件类型包括:
    • ascii:默认值。网络 ASCII
    • binary:二进制映像,需要使用二进制方式的文件类型包括 ISO 文件、可执行文件、压缩文件、图片等。此方式比 ascii 更有效
    • ebcdic:
    • image:
    • local M:本地类型。M 参数定义每个计算机字位的十进制数
    • tenex:
  • 交互式提示:使用 mget 或 mput 时,prompt命令让 ftp 在文件传输前进行提示,防止覆盖已有的文件。若发出 prompt 命令时已经启动了提示,ftp 将关掉提示,此时再传输所有的文件则不会有任何提示

shell if

  • shell 支持 3 中 if 语句

    • if...fi
    • if...else...fi
    • if...elif...else...fi

      if [ $useEncryption != "false" ] && [ $softEncryption != "false" ]
      then
      # do sth
      elif [ $useEncryption != "false" ] && [ $softEncryption == "false" ]
      then
      # do sth
      elif [ $useEncryption == "false" ] && [ $softEncryption != "false" ]
      then
      # do sth
      elif [ $useEncryption == "false" ] && [ $softEncryption == "false" ] # or else
      then
      # do sth
      fi
      

shell 操作符

  • 讨论 Bourne shell(默认的 shell) 支持的操作符

算术操作符

  • Bourne shell 不支持运算符,但是可以使用awk或者expr外部程序,例如res=`expr 2 + 2`; echo $res
  • 运算符和表达式之间必须有空格
  • Bourne shell 支持的算术运算符包括:(假如a=10; b=20
    • 加法+,例如expr $a + $b,结果是 30
    • 减法-,例如expr $a - $b,结果是 -10
    • 乘法*,例如expr $a \* $b,结果是 200
    • 除法/,例如expr $b / $a,结果是 2
    • 取模%,例如expr $a % $b,结果是 0
    • 赋值=,例如a = $b,结果是 a 被赋值 20
    • 等于==,例如[$a == $b],结果是 false
    • 不等于!=,例如[$a != $b],结果是 true
  • 条件表达式必须在方括号中,且两端有空格隔开

关系操作符

  • 只对数值生效,string 作为操作数是无效的,例如对1020或者"10""10"生效,但是对tentwenty无效
    • 无效的 string 是指 string 中包含非数字的字符
  • Bourne shell 支持的关系型操作符包括:(假如a=10; b=20
    • 等于-eq,例如[ $a -eq $b ],结果是 false
    • 不等于-ne,例如[ $a -ne $b ],结果是 true
    • 大于-gt,例如[ $a -gt $b ],结果是 false
    • 小于-lt,例如[ $a -lt $b ],结果是 true
    • 大于等于-ge,例如[ $a -ge $b ],结果是 false
    • 小于等于-le,例如[ $a -le $b ],结果是 true
  • 条件表达式必须在方括号内,且两端有空格分开
  • 其他 shell 可能支持的操作
    • 等于==,例如(( $a == $b )),结果是 false
    • 不等于!=,例如[ $a != $b ],结果是 true
    • 大于>,例如(( $a > $b )),结果是 false
    • 小于<,例如(( $a < $b )),结果是 true
    • 大于等于>=,例如(( $a >= $b )),结果是 false
    • 小于等于<=,例如(( $a <= $b )),结果是 true

布尔操作符

  • Bourne shell 支持的布尔操作符包括:(假如a=10; b=20
    • 逻辑取否!,例如[ ! false ],结果是 true
    • 逻辑或-o,例如[ $a -lt 20 -o $b -gt 100 ],结果是 true
    • 逻辑与-a,例如[ $a -lt 20 -a $b -gt 100 ],结果是 false

string 操作符

  • Bourne shell 支持的字符串操作符包括:(假如a="abc"; b="efg"
    • 等于=,例如[ $a = $b ],结果是 false
    • 不等于!=,例如[ $a != $b ],结果是 true
    • 字符串为 null,即长度为 0 -z,例如[ -z $a ],结果是 false
    • -z的字符串可以不被双引号引用
    • 字符串不为 null,即长度不为 0 -n,例如[ -n $a ],结果是 true
    • -n的字符串建议用双引号引用,不加双引号可以使用! -z
    • 字符串是否不是空串str,例如[ $a ],结果是 true
  • 其他 shell 可能支持的操作
    • 大于>,例如[[ $a > $b ]][ $a \> $b ],结果是 false
    • 小于<,例如[[ $a < $b ]][ $a \< $b ],结果是 true

文件测试运算符

  • 假设文件test的大小是 100 Bytes,且有读写和可执行权限,file="test"
  • -b file是否是块文件,例如[ -b $file ],结果是 false
  • -c file是否是字符文件,例如[ -c $file ],结果是 false
  • -d file是否是文件夹,例如[ -d $file ],结果是 false
  • -f file是否是普通文件(不是块/字符文件,也不是文件夹),例如[ -f $file ],结果是 true
  • -g file是否设置了group ID 位,即 SGID,例如[ -g $file ],结果是 false
  • -k file是否设置了 sticky 位,例如[ -k $file ],结果是 false
  • -p file是否是一个命名的管道,例如[ -p $file ],结果是 false
  • -t file文件描述符是否打开且和一个终端相关,例如[ -t $file ],结果是 false
  • -u file是否设置了 user ID 位,即 SUID,例如[ -u $file ],结果是 false
  • -r file是否可读,例如[ -r $file ],结果是 true
  • -w file是否可写,例如[ -w $file ],结果是 true
  • -x file是否可执行,例如[ -x $file ],结果是 true
  • -s file文件大小是否大于 0,例如[ -s $file ],结果是 true
  • -e file文件是否存在,如果是一个存在的文件夹也返回 true,例如[ -e $file ],结果是 true

C Shell 操作符

Korn Shell 操作符

字符串截取

  • 按指定长度截取

    • ${str:n1:n2}从左边第 n1+1 个字符串开始,长度为 n2
    • ${str:0-n1:n2}从右边第 n1 个字符串开始,长度为 n2
    • ${str:n1}从左边第 n1+1 个字符串开始到最后

      var="aa1c10e139bf750b3335e896f366665cfa40d95f"
      
      echo ${var:1:3}   #a1c
      echo ${var:0-1:3} #f
      echo ${var:0-4:3} #d95
      echo ${var:1}     #a1c10e139bf750b3335e896f366665cfa40d95f
      

here document

  • here document 是一种重定向方式,基本形式如下

    • 将两个 delimiter 直接的内容传递给 cmd 作为输入参数
    • >是终端产生的提示输入信息的标识符

      cmd << delimiter
      here document content
      delimiter
      
  • 例如在终端向一个文件写入多行

    cat << EOF > output.sh
    echo "hello"
    echo "world"
    EOF
    
  • 内容可以包括普通字符,也可以使用变量

    • 执行脚本sh output.sh haha$1被替换为haha
    • 不想展开变量,则在起始的 delimiter 前后添加引号实现cat << "EOF" > output.sh

      cat << EOF > output.sh
      echo "hello"
      echo $1
      EOF
      
  • 使用<<-而不是<<,可以将 here document 的内容每行前面的制表符删掉,便于编写的时候将内容部分缩进

参考网站

相关