如何优雅地编写shell脚本

Linux shell的脚本语言可以说是一门非常有趣而独特的编程语言,下面列举一些我接触到的比较有意思的小技巧,它们虽然不一定能真的在编程中用得上,但对于各位理解shell有着十分重大的意义。

什么都不做:冒号(:)命令

它看起来没什么卵用,但这条空命令实际上还有另一个功能:什么都不做然后返回0状态码(表示正常退出)。因为这个特性,它其实是一条万能命令,利用它可以结束掉一些很麻烦的异常处理。
同时它也是重定向的神兵利器,比如利用它可以初始化一个文件(如果文件存在就清空其内容)。

: > foo.log 

伪三目运算符

在编写程序时,使用三目运算符通常能使程序更加优雅。但是遗憾的是,shell脚本并没有三目运算符。但是我们可以通过一些小手段实现伪三目运算符。

# 如果 $foo比$bar大,则给$baz赋值伪0,否则给$baz赋值为1
[ $foo -ge $bar ] && baz=0 || baz=1

这让我想起来Lua等脚本语言常用的编程技巧,如为函数的一个可选参数设定一个默认值时可以这么写a = a or 0;或者统计一篇文章中所有单词出现的频数CountTable[a] = CountTable[a] and CountTable[a] + 1 or 1
利用这种短路运算的特性,可以实现很多很优雅的编程写法,而不仅局限于三目运算符。

给变量设定默认值

在引用某一个参数时,如果这个参数还没被定义,会被当做空字符串处理。但是在实际编程中我们经常要设定其他的默认值,那么我们可以这么做。

#将$bar的值赋给$foo,但如果$bar未定义则将$foo赋值为0
foo=${bar:-0}

顺带一提,如果不是-而是=,则连同$bar一起赋值为0

#将$bar的值赋给$foo,但如果$bar未定义则将$foo和$bar一起赋值为0
foo=${bar:=0}

获得正在被执行的脚本文件所在的目录绝对路径

相信这也是编程中经常遇到的需求。对于以下的脚本666.sh(位于/home/luweirong/Downloads),企图通过pwd获得这个脚本文件本身所在的目录绝对路径。

#!/bin/bash
pwd

但如果在/home/luweirong下执行它

./Downloads/666.sh

得到的结果将会是:/home/luweirong,这做法显然不可行。因此要正确访问目标资源,必须使用绝对路径,而这首先要获得该脚本文件所在的目录绝对路径。
以下的这条命令,可以直接复制粘贴,并且适用于任何场合。

script_dir_path=$(dirname $(readlink -f $0))

而且它的神奇之处还在于,如果运行的是该脚本文件的一个符号链接,$script_dir_path的值依然是该脚本文件(而不是它的链接)所在目录的绝对路径。但是如果是硬链接,其值将还只是该链接文件所在目录的绝对路径。
但是在BSDOSX等系统中readlink命令的运行机制有所不同,因此可以选择coreutils软件包里面的greadlink (GNU readlink) 代替。

script_dir_path=$(dirname $(greadlink -f $0))s

设定当脚本被中终止运行时自动执行的动作

ShellScript在运行的过程中经常要生成一些临时文件,作为数据的中转站,利用完毕后就把它删除。如果在生成文件后,还没来得及处理和删除,程序突然被终止,就会残留一些垃圾文件。或者更严重的,你修改或者移动了某些关键文件,然而没来得及修改回来,程序就被终止了。(当然,在正常终止的情况下也同样需要处理这些文件)
这个时候trap命令就闪亮登场了。

trap "
  mv /tmp/swap-file original-file
  rm /tmp/target-file
" 0

上面命令最后的0是一个信号码,它会在程序终止时,向程序本身送出(这样既适用于正常退出,又适用于异常终止)。要用好trap命令,必须理解好Linux的信号机制。除了经常使用的用于终止程序运行的SIGTERMSIGKILL等信号,还有数十种其它信号,可以使用kill -l命令查看它们。

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
30) SIGPWR      31) SIGSYS      35) SIGRTMIN    36) SIGRTMIN+1
37) SIGRTMIN+2  38) SIGRTMIN+3  39) SIGRTMIN+4  40) SIGRTMIN+5
41) SIGRTMIN+6  42) SIGRTMIN+7  43) SIGRTMIN+8  44) SIGRTMIN+9
45) SIGRTMIN+10 46) SIGRTMIN+11 47) SIGRTMIN+12 48) SIGRTMIN+13
49) SIGRTMIN+14 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8
57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4
61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1  64) SIGRTMAX

由于0不能通过kill主动发送,所以你在上面看不到它。

创建临时文件

正如上面所说,创建临时文件在shell脚本编写的过程中十分常见,但每次都要专门为这些文件想一个尽量无冲突的名字真的挺麻烦的。这时候,mktemp命令就能派上用场了。

temp_file=$(mktemp) #在/tmp文件夹下生成一个随机名字的文件,比如: /tmp/tmp.C3N9Ng6IaU
temp_dir=$(mktemp -d) #在/tmp文件夹下生成一个随机名字的目录,比如: /tmp/tmp.wDOVMXVcio

既然是临时文件,也别忘了做善后处理。

#生成临时文件
temp_file=$(mktemp)
temp_dir=$(mktemp -d)

#定义善后处理动作
trap "
rm $temp_file
rm $temp_dir
" 0

你可能感兴趣的:(Linux)