跳转至

BASH 的一些技巧

本文记录阅读 Advanced Bash-Scripting Guide 时,新获得的一些技巧。

Chapter 3. Special Characters

进制转换

# 把制定的进制数转成十进制数
# 2#101011 # 之前的数字是进制数的基数, 101011 是进制数
echo $(( 2#101011 ))    # 43
echo $(( 16#072f3 ))    # 29427

: 用于占位符

# : 用于占位符 
: ${username=`whoami`}
# ${username=`whoami`}   Gives an error without the leading :
#                        unless "username" is a command or builtin...

: ${1?"Usage: $0 ARGUMENT"}     # From "usage-message.sh example script.

: 用于评估变量

: ${HOSTNAME?} ${USER?} ${MAIL?}
#  Prints error message
#+ if one or more of essential environmental variables not set.

{} 将代码块的输出保存到文件

这种方式节省了每次输出都要重定向的麻烦,同时也不需要使用全局重定向的模式。方便记录日志。

{ # Begin code block.
  echo
  echo "Archive Description:"
  rpm -qpi $1       # Query description.
  echo
  echo "Archive Listing:"
  rpm -qpl $1       # Query listing.
  echo
  rpm -i --test $1  # Query whether rpm file can be installed.
  if [ "$?" -eq $SUCCESS ]
  then
    echo "$1 can be installed."
  else
    echo "$1 cannot be installed."
  fi  
  echo              # End code block.
} > "$1.test"       # Redirects output of everything in block to file.

echo "Results of rpm test in file $1.test"

Chapter 4. Introduction to Variables and Parameters

获取最后一个命令参数的一些技巧

args=$#           # Number of args passed.
lastarg=${!args}
# Note: This is an *indirect reference* to $args ...


# Or:       lastarg=${!#}             (Thanks, Chris Monson.)
# This is an *indirect reference* to the $# variable.
# Note that lastarg=${!$#} doesn't work.

Chapter 10. Manipulating Variables

字符串截取

# ${string:position:length} 从 position 开始,截取 length 长度的字符串
stringZ=abcABC123ABCabc
#       0123456789.....
#       0-based indexing.

echo ${stringZ:0}                            # abcABC123ABCabc
echo ${stringZ:1}                            # bcABC123ABCabc
echo ${stringZ:7}                            # 23ABCabc

echo ${stringZ:1:-2}                         # bcABC123ABCa

# 反向截取有意思的地方来了
# Is it possible to index from the right end of the string?

echo ${stringZ:-4}                           # abcABC123ABCabc
# Defaults to full string, as in ${parameter:-default}.
# However . . .

echo ${stringZ:(-4)}                         # Cabc 
echo ${stringZ: -4}                          # Cabc
# Now, it works.
# Parentheses or added space "escape" the position parameter.

Chapter 36. Miscellany

优化

禁用 Unicode 支持

如果不需要 Unicode 支持,可以禁用,这样可以提高一些性能。

export LC_ALL=C

使用内置命令而不是外部命令

使用内置的命令比使用外部命令要快很多,因为不需要创建新的进程。

以字符串匹配为例,使用内置的字符串匹配比使用 grep 要快很多。

v="Builtins execute faster and usually do not launch a subshell when invoked."

# Using grep
for i in {1..1000}
do
    echo $v | grep -q  'f*r'
done

# Using BASH pattern matching
for i in {1..1000}
do
    [[ $v == *f*r* ]]
done
exit $?

使用 grep 匹配的方式执行需要 3.7 秒,而后者只需要 0.008 秒,两者相差 462.5 倍。

使用(()) 而不是 expr 执行算数运算

#!/bin/bash
#  test-execution-time.sh
#  Example by Erik Brandsberg, for testing execution time
#+ of certain operations.
#  Referenced in the "Optimizations" section of "Miscellany" chapter.

count=50000
echo "Math tests"
echo "Math via \$(( ))"
time for (( i=0; i< $count; i++))
do
  result=$(( $i%2 ))
done

echo "Math via *expr*:"
time for (( i=0; i< $count; i++))
do
  result=`expr "$i%2"`
done

echo "Math via *let*:"
time for (( i=0; i< $count; i++))
do
  let result=$i%2
done

echo
echo "Conditional testing tests"

echo "Test via case:"
time for (( i=0; i< $count; i++))
do
  case $(( $i%2 )) in
    0) : ;;
    1) : ;;
  esac
done

echo "Test with if [], no quotes:"
time for (( i=0; i< $count; i++))
do
  if [ $(( $i%2 )) = 0 ]; then
     :
  else
     :
  fi
done

echo "Test with if [], quotes:"
time for (( i=0; i< $count; i++))
do
  if [ "$(( $i%2 ))" = "0" ]; then
     :
  else
     :
  fi
done

echo "Test with if [], using -eq:"
time for (( i=0; i< $count; i++))
do
  if [ $(( $i%2 )) -eq 0 ]; then
     :
  else
     :
  fi
done

exit $?

上述脚本执行的结果如下:

Math tests
Math via $(( ))

real    0m0.130s
user    0m0.130s
sys 0m0.001s
Math via *expr*:

real    2m41.665s
user    1m10.645s
sys 1m36.772s
Math via *let*:

real    0m0.180s
user    0m0.176s
sys 0m0.004s

Conditional testing tests
Test via case:

real    0m0.146s
user    0m0.142s
sys 0m0.005s
Test with if [], no quotes:

real    0m0.186s
user    0m0.184s
sys 0m0.002s
Test with if [], quotes:

real    0m0.201s
user    0m0.196s
sys 0m0.005s
Test with if [], using -eq:

real    0m0.195s
user    0m0.192s
sys 0m0.003s

从结果来看,使用 (( )) 进行算数运算比使用 expr 要快很多,两者相差 12351.3 倍。

安全问题

隐藏 shell 脚本源代码

可以使用 shc --generic shell script compiler 来编译 shell 脚本为二进制可执行文件,这样可以隐藏脚本的源代码。