Shell Script Tutorials

Shell 简介

Shell是用户和 Linux操作系统之间的接口, 也可以看作是命令行的解释器.
常见的 Shell Script解释器有:

  • sh: 即 Bourne shell,POSIX(Portable Operating System Interface)标准的shell解释器,它的二进制文件路径通常是/bin/sh,由Bell Labs开发。
  • bash: Bash是Bourne shell的替代品,属GNU Project,二进制文件路径通常是/bin/bash。
  • zsh: zsh是由Paul Falstad于1990年创建的,它是一个Bourne风格的shell,它包含了bash中的功能,甚至更多。 例如,zsh具有拼写检查功能,可以监视登录/注销,某些内置编程功能(如字节码),支持语法中的科学计数,允许浮点运算和更多功能。

Shell 脚本文件开头一般会声明用哪种脚本解释器:

#!/bin/bash

如何切换Shell

切换到 zsh:

chsh -s /bin/zsh

切换到 bash:

chsh -s /bin/bash

变量, 数据类型

  • 变量定义: var=value注意等号两边没有空格,推荐用双引号把右值引用起来
  • 调用已经定义的变量$var${var};
  • declare的使用点这里.
  • Shell提供语法检测变量是否赋值:
    • ${var:-value} : 如果var存在且非空, 整个表达式的值是var; 如果var为空或者未定义, 表达式的值是value;
    • ${var:=value} : 如果var存在且非空, 整个表达式的值是var; 如果var为空或者未定义, 表达式的值是value, 并且给var赋值;
    • ${var:+mesg} : 测试var, 如果var存在且非空,则${var:+mesg}的返回值为mesg;如果var为空或未定义,则返回null
  • 如果是把一个命令的结果赋值给某变量:
    var=`date`
    #或者
    var=$(date)

内置变量

  • HOME, PS1, 等等
  • $0: 脚本自身的名称
  • $1 ~ $n : 每个参数, 例如$1是第一个参数
  • $#: 所有参数的数量
  • $* : 所有参数的组成的字符串,比如命令test.sh param1 param2, 其$*的值是”param1 param2”;
  • $@: 所有参数, 和上面不同的是: $@是由多个子字符串组成的, $@的内容是”param1”, “param2” 这两个字符串;
  • $?: 上个命令退出状态码, 0表示成功
  • $$: 当前Shell进程的PID
  • $!: Shell最后运行的后台Process的PID

数值计算

var1=$((1 + 3))
var2=$((var1 + 1)) ## 注意var1前没有
var3=`expr $var2 + 1`

数组

  • @*可以获取数组所有元素: ${my_array[*]} , ${my_array[@]}
  • 数组长度: ${#array[*]}, #号在shell里表示长度
arr1[0]="cy"
arr1[1]="kz"
arr1=(java,php,python) # 此时[0]和[1]都被覆盖了

# 遍历数组
echo ${arr1[*]} # 以一个字符串打印所有元素
echo ${arr1[@]} # 每个元素作为一个字符串
echo ${#arr1[*]} # 数组元素个数, 卧槽,,, 这语法

# 清空
unset arr1[0] # 仅清空一个元素
unset arr1[*] # 清空所有

字符串

  • 定义: str="hello"
  • 拼接: str=$str"world"
  • 字符串长度: ${#str}
  • 替换: ${源字符串/查找字串/替换字串} : 一个’/‘表示替换第一个’//‘表示替换所有,当查找出中出现了:”/“需要转义成”\/“

常用的字符串替换: arr=(${string//,/ }) 可以实现把逗号分隔的字符串转成数组

字符串续行

shell字符串续行符 “\”,来把一行长字符串分解成多行:

# Example1: echo将输出一行 "continuation     lines"
echo "continuation \
lines"


# Example2: 把一条长命令写为多行
commannd1 arg1 arg2 \
arg3 arg4


# Example3: 定义一个字符串, 写为多行, 但字符串里不包括换行符
var="this is \
continuation"

常用类型比较

包括文件/字符串/数字比较

文件比较

  • -e filename 如果 filename 存在,则为真
  • -d filename 如果 filename 为目录,则为真
  • -f filename 如果 filename 为常规文件,则为真
  • -L filename 如果 filename 为符号链接,则为真

Example:

if [ ! -f /tmp/foo.txt ]; then
echo "File not found!"
fi

字符串比较

  • -z string 如果 string 长度为零,则为真
  • -n string 如果 string 长度非零,则为真
  • string1 = string2 如果 string1 与 string2 相同,则为真
  • string1 != string2 如果 string1 与 string2 不同,则为真
  • "$str" == "This's String" 变量比较字符串
  • "$sub" =~ " $string " 变量$sub是否包含在字符串$string里, 注意两个空格

判断字符串是空串:

if [ "$str" == "" ];then
echo NULL
fi

数字比较

  • num1 -eq num2 数字比较, 等于则为真
  • num1 -lt num2 小于
  • num1 -gt num2 大于
  • num1 -ge num2 大于或等于

Shell Scripts中的符号总结

井号

#井号: 求长度

  • 字符串的长度: ${#string}
  • 数组的长度: ${#array[*]}

大中小括号

  • ()小括号, 三种用途
    • 命令组: (cmd1; cmd2)括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被括号外的命令使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
    • 命令替换: $(cmd) 等同于 `cmd`,原理是 shell先扫描一遍命令行,发现了该行里有$(cmd)结构,便将$(cmd)中的cmd执行一次,得到其标准输出,再将此输出放到原来命令。例如 airportd_pid=$(ps -ef | grep airportd | grep -v grep | awk '{print $2}')
    • 定义数组: array=(a b c d)
  • $(())双小括号: 数值计算, 不支持浮点型。例如 $((exp))结构扩展并计算一个算术表达式的值,如果表达式的结果为0,那么返回的退出状态码为1(false),而一个非零值的表达式所返回的退出状态码将为0(true)。若是逻辑判断,表达式exp为真则为1,假则为0。

    # 例1:
    echo $(( groupnum*100 ))

    # 例2: 数值与运算符可以没有空格,变量的使用时也可以不使用$num
    while ((num<100))
    do
    echo "$num"
    ((num=num*2))
    done
  • []中括号: 见”if-then-else”

  • [[ ]]双中括号: 见”多重条件if判断”
  • {}花括号: 变量替换, 例如 ${var}

反引号

反引号用于命令替换, 同上面的$(cmd), 例如: count=`echo $var | sed 's/^-//'` 这行命令截去var前置的负号

控制流程和语句

顺序执行

  • cmd1 ; cmd2 : 命令顺序执行,命令之间不存在关系,互不影响
  • cmd1 && cmd2 : cmd1返回0(成功)那么才执行cmd2
  • cmd1 || cmd2 : cmd1返回非0(不成功)那么才执行cmd2

grouping commands

(cmd1; cmd2)

(cmd1; cmd2):注意cmd2后面没有分号,() 将command group置于sub-shell(子shell) 中去执行,也称 nested sub-shell。
括号内对环境变量的修改,不会影响括号外面。

{cmd1; cmd2;}

{cmd1; cmd2;}:注意最后一个语句后有分号,{} 则是在同一个shell内完成,也称 non-named command group。
Shell的函数跟 non-named command group 是类似的,每个命令之间可以用分号或者换行符隔开。

if-then-else

  • []前后都要有空格, if[之间也需要有空格(好操蛋的语法):
if [ -d /root/fff ]; then
do_something
elif [ $timeofday = "no" ]; then
do_something2
else
do_something3
fi

if [ $a == "true" ]; then 中的then可以写在第二行, 下面的语法也是正确的:

if [ status ]
then
do_something
else
do_something2
fi

多重条件if判断:

if [ "$bool1" == true ] || [ "$bool1" == true ] && [ "$bool1" != true ]; then echo 7; fi #1, no output, due to && IS NOT higher precedence than ||
if [ "$bool1" == true ] || { [ "$bool1" == true ] && [ "$bool1" != true ] ;}; then echo 7; fi #not same like #1
if { [ "$bool1" == true ] || [ "$bool1" == true ] ;} && [ "$bool1" != true ]; then echo 7; fi #same like #1

if [[ "$bool1" == true || "$bool1" == true && "$bool1" != true ]]; then echo 7; fi #1 #print 7, due to && higher precedence than ||
if [[ "$bool1" == true ]] || { "$bool1" == true && "$bool1" != true ;}; then echo 7; fi #same like #1
if { "$bool1" == true ]] || "$bool1" == true ;} && [[ "$bool1" != true ]] ; then echo 7; fi #not same like #1

if in one line

if [status]; then do_something; else do_something; fi

for-do-done

for var in 1 2 3; do
echo $var
done

# 也可以写
for var in 1 2 3
do
echo $var
done

# 对ls返回的内容循环
for f in $(ls);
do echo "$f"
done


# 或
for f in *; do
echo "$f"
done

# 循环所有子目录
for dir in */; do
echo "$dir"
done

# 对一个文件每行循环
cat file | while read line; do
echo $line;
done

# 对一个数组循环
array=(1 2 3)
for var in ${array[@]}; do
{
echo $var
} & ## 并行执行
done

for in one line

循环语句写为一行: for var in a b c; do echo $var; done

while

注意中括号[前后的空格:

$i=0
while [ $i -lt 5 ]
do
do_something
done

switch

case $var in
string1)
# do_something
;;
string2)
# do_something
;;
*)
echo "default"
exit 1
;;
esac

函数

shell中的函数都不带参数, 在函数内用$1$2作为参数

doSomething() {
echo "hello"
}

# 调用
doSomething param1 param2 # 没有分号!

重定向

详见: [Linux Primer.md]

Example:

  • command > outfile 2>&1 &
  • command < infile > outfile
  • Here document:
    wc -l << EOF
    hello
    world
    EOF

Learn Shell in Y Minutes

【Learn Shell in Y Minutes】 是一个很有意思的项目, 用一段简单的代码介绍 Shell中的各种语法,
地址: Learn X in Y Minutes: Scenic Programming Language Tours

# 定义数组
# 变量定义,=号前后都不能有空格, 这奇葩语法规则...
hosts=(10.10.2.58:8080 10.10.2.58:8090 10.10.50.71:8080)

# 访问数组全部
echo ${hosts[@]} > test
echo ${hosts[*]}>> test

# 命令给变量赋值
line=`cat test | sed "s/ /\n/g" | wc -l`
# 这是另一种:
line=$(cat test | sed "s/ /\n/g" | wc -l)

# if, 中括号[]前后要有空格
if [ $line -gt 0 ]
then
echo 'line='$line
fi

# while
i=0
while [ $i -lt $line ]
do
echo ${hosts[$i]}
i=$((i+1))
# i=`expr $i+1`
# i=$(expr $i+1)
done

for host in ${hosts[@]}
do
ip=`echo $host | cut -d : -f 1`
port=`echo $host | cut -d : -f 2`
echo $ip $port
done