Shell脚本语法

1. Shell脚本简介
Shell 脚本(shell script),是一种为 shell 编写的脚本程序。业界所说的 shell 通常都是指 shell 脚本,但读者朋友要知道,shell 和 shell script 是两个不同的概念。由于习惯的原因,简洁起见,本文出现的 "shell编程" 都是指 shell 脚本编程,不是指开发 shell 自身。
Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • .......
    在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

2. Shell语法练习

#!/bin/bash

# ================== 创建 执行脚本 ==================

# 1.创建shell脚本, 打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell),
#   扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了。
# 随便写点东西
echo "hello word!"

# 2.执行脚本有两种方法

# 1)、作为可执行程序
# 将上面的代码保存为 test.sh,并 cd 到相应目录:
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本
# 注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,
# linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,
# 你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。

# 2)、作为解释器参数
# 这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:
/bin/sh test.sh
/bin/php test.php
# 这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

# 3.注释
# #开头就是单行注释
# 多行注释1
:<<EOF
    ---
EOF
# 多行注释2 使用其他符号
:<<!
    ---
!

# ================== 输出 ==================
# 1.echo
echo -e "OK \n" #-e:开启转义 \n:换行 \c:不换行
echo "hello word!"

# 2.printf
# 默认 printf 不会像 echo 自动添加换行符,可以手动添加 \n
# 语法: printf  format-string  [arguments...]  (format-string: 为格式控制字符串, arguments: 为参数列表)
printf "hello shell! \n"
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg  
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234 
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543 
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876 
:<<EOF
%s %c %d %f都是格式替代符
%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。
EOF

# format-string为双引号
printf "%d %s\n" 1 "abc"
# 单引号与双引号效果一样 
printf '%d %s\n' 1 "abc" 
# 没有引号也可以输出
printf %s abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def
printf "%s\n" abc def
printf "%s %s %s\n" a b c d e f g h i j
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n" 

# printf的转义序列
:<<EOF
\a  警告字符,通常为ASCII的BEL字符
\b  后退
\c  抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f  换页(formfeed)
\n  换行
\r  回车(Carriage return)
\t  水平制表符
\v  垂直制表符
\\  一个字面上的反斜杠字符
\ddd    表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd   表示1到3位的八进制值字符
EOF



# ================== 定义变量 ==================
# 运行shell时, 会同时存在三种变量:局部变量 环境变量 shell变量
# 1.定义变量
# 等号两边不能有空格, 使用变量时前面加$, {}可加可不加
your_name="mayun"
echo $your_name
echo ${your_name}
your_name="huang"
echo $your_name
# 变量加{}的意义
for skill in Ada Coffe Action Java; do
    echo "I am good at ${skill}Script"
done

# 2.只读变量
# 只读变量加 readonly
myUrl="http://www.baidu.com"
readonly myUrl
# 运行结果 test.sh: line 31: myUrl: readonly variable
#myUrl="http://www.google.com" 

# 3.删除变量
# unset 删除变量, 删除后不能再使用, 不能删除只读变量
a="baidu"
unset a
echo $a #执行没反应


# ================== 字符串 ==================
# 字符串可以用单引号'', 也可以用双引号"", 也可以不用引号

# 1.单引号
# 特点
# a. 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
# b. 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
strA='this is a string'

# 2.双引号
# 特点
# a. 双引号里可以有变量
# b. 双引号里可以出现转义字符
strB='Li'
strC="Hello, I know your are \"$strB\" ! \n"
echo $strC

# 3.字符串拼接
# a. 上引号拼接
strD="hello, "$strB" !"
strE="hello, ${strB} !"
echo $strD $strE # hello, Li ! hello, Li !
# b. 单引号拼接
strF='hello, '$strB' !'
strG='hello, ${strB} !'
echo $strF $strG # hello, Li ! hello, ${strB} !

# 4.字符串长度
strH="abcd"
echo ${#strH} # 4

# 5.字符串截取
strI="what are your neng sha lei ?"
echo ${strI:0:4} # what

# 6.字符串查找 报错
# strH="runoob is a great site"
# echo `expr index "$strH" io`  # 输出 4



# ================== 数组 ==================
# bash支持一维数组(不支持多维数组),并且没有限定数组的大小

# 1.定义数组
# 用小括号表示数组, 元素用空格分开, 数组名=(值1 值2 ... 值n)
array_a=(v1 v2 v3 v4 v5)
array_b=(
v1
v2
v3
)
# 单独定义各个元素
array_c[0]=v1
array_c[1]=v2

# 2.读取数组
# 一般格式是: ${数组名[下标]}
valuen1=${array_a[0]}
echo $valuen1
# 使用@可以读取所有元素
echo ${array_a[@]}

# 3.获取数组长度
# 与获取字符串长度一样
# 获取数组元素个数
echo ${#array_a[@]}
echo ${#array_a[*]}
# 获取数组单个元素长度
echo ${#array_a[4]} # 2



# ================== 传递参数 ==================
# 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
echo "Shell 传递参数实例!"
echo "执行的文件名:$0"
echo "第一个参数为:$1"
echo "第二个参数为:$2"
echo "第三个参数为:$3"

# 特殊字符处理参数
:<<EOF
$#  传递到脚本的参数个数
$*  以一个单字符串显示所有向脚本传递的参数。如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$$  脚本运行的当前进程ID号
$!  后台运行的最后一个进程的ID号
$@  与$*相同,但是使用时加引号,并在引号中返回每个参数。如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
$-  显示Shell使用的当前选项,与set命令功能相同。
$?  显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

$* 与 $@ 区别:
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。
EOF

echo "参数个数为:$#"
echo "传递的参数作为一个字符串显示:$*"

echo "-- \$* 演示 ---"
for i in "$*"; do
    echo $i
done

echo "-- \$@ 演示 ---"
for i in "$@"; do
    echo $i
done



# ================== 基本运算 ==================
:<<EOF
Shell 和其他编程语言一样,支持多种运算符,包括:算数运算符 关系运算符 布尔运算符 字符串运算符 文件测试运算符
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加(注意使用的是反引号 而不是单引号), 表达式和运算符之间要有空格
EOF

# 1.算术运算
# 假定变量 a 为 10,变量 b 为 20:
# + 加法  `expr $a + $b` 结果为 30。
# - 减法  `expr $a - $b` 结果为 -10。
# * 乘法  `expr $a \* $b` 结果为  200。
# / 除法  `expr $b / $a` 结果为 2。
# % 取余  `expr $b % $a` 结果为 0。
# = 赋值  a=$b 将把变量 b 的值赋给 a。
# ==    相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。
# !=  不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。

# 注意:
# 乘号(*)前边必须加反斜杠(\)才能实现乘法运算;
# if...then...fi 是条件语句,后续将会讲解。
# 在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "\" 。

val1=`expr 1 + 1`
echo $val1

a=10 b=20
if [ $a != $b ]
then
    echo "a 不等于 b"
fi

if [[ $a != $b ]]; then
    echo 123456
fi

# 2.关系运算符
:<<EOF
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:
运算符 说明  举例
-eq 检测两个数是否相等,相等返回 true。    [ $a -eq $b ] 返回 false。
-ne 检测两个数是否不相等,不相等返回 true。  [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。   [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。   [ $a -le $b ] 返回 true。
EOF

if [[ $a -eq $b ]]
then
    echo "a 等于 b"
else
    echo "a 不等于 b"
fi

# 3.布尔运算符
:<<EOF
下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
运算符 说明  举例
!   非运算,表达式为 true 则返回 false,否则返回 true。  [ ! false ] 返回 true。
-o  或运算,有一个表达式为 true 则返回 true。  [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a  与运算,两个表达式都为 true 才返回 true。  [ $a -lt 20 -a $b -gt 100 ] 返回 false。
EOF

if [ $a -lt 100 -a $b -gt 15 ]
then
   echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
   echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi

# 4.逻辑运算
:<<EOF
以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
运算符 说明  举例
&&  逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
||  逻辑的 OR  [[ $a -lt 100 || $b -gt 100 ]] 返回 true
EOF

if [[ $a -lt 100 && $b -gt 100 ]]
then
   echo "返回 true"
else
   echo "返回 false"
fi

# 5.字符串运算符
:<<EOF
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":
运算符 - 说明     - 举例
=   检测两个字符串是否相等,相等返回 true。  [ $a = $b ] 返回 false。
!=  检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z  检测字符串长度是否为0,为0返回 true。  [ -z $a ] 返回 false。
-n  检测字符串长度是否为0,不为0返回 true。 [ -n "$a" ] 返回 true。
$   检测字符串是否为空,不为空返回 true。   [ $a ] 返回 true。
EOF
a1="abc"
if [ -z $a1 ]
then
   echo "-z $a1 : 字符串长度为 0"
else
   echo "-z $a1 : 字符串长度不为 0"
fi


# ================== 流程控制 ==================
# 1.if eles fi
# 格式
:<<EOF
a.
if condition
then
    command1 
    command2
    ...
    commandN 
fi

b.
if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi

c.
if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi
EOF
# 写成一行: if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi


#if else语句经常与test命令结合使用,如下所示:
num1=$[2*8]
num2=$[1+5]
if test $[num1] -eq $[num2]
then
    echo '两个数字相等!'
else
    echo '两个数字不相等!'
fi


# 2.for 循环
# 格式
:<<EOF
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done
EOF
# 写成一行: for var in item1 item2 ... itemN; do command1; command2… done;
for num in 1 2 3 4 5
do
    echo "the value is: $num"
done

for str in what are you doing; do
    echo "the word is: $str"
done

# 3.while 语句
# 格式
:<<EOF
while condition
do
    command
done
EOF

int=1
while (($int <= 5))
do
    echo $int
    let "int++" # 使用 Bash let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量
done

# 4.until 循环
:<<EOF
until 循环执行一系列命令直至条件为 true 时停止。
until 循环与 while 循环在处理方式上刚好相反。
一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
until 语法格式:
until condition
do
    command
done
EOF

# 5.case
:<<EOF
 case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:
case 值 in
模式1)
    command1
    ;;
模式2)
    command1
    ;;
esac
case工作方式如上所示。取值后面必须为单词in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
EOF

# echo '输入 1 到 4 之间的数字:'
# echo '你输入的数字为:'
# read aNum
# case $aNum in
#     1)  echo '你选择了 1'
#     ;;
#     2)  echo '你选择了 2'
#     ;;
#     3)  echo '你选择了 3'
#     ;;
#     4)  echo '你选择了 4'
#     ;;
#     *)  echo '你没有输入 1 到 4 之间的数字'
#     ;;
# esac

# 6.跳出循环
# 1)break命令 break命令允许跳出所有循环(终止执行后面的所有循环)。
# while :
# do
#     echo -n "输入 1 到 5 之间的数字:"
#     read aNum
#     case $aNum in
#         1|2|3|4|5) echo "你输入的数字为 $aNum!"
#         ;;
#         *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
#             break
#         ;;
#     esac
# done

# 2)continue continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。


# ================== Shell 函数 ==================
# 1.格式
:<<EOF
[ function ] funname [()]
{
    action;
    [return int;]
}
说明:
1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255
EOF

demoFun(){
    echo "这是第一个shell函数"
}
demoFun

funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum 和 $anotherNum !"
    return $(($aNum+$anotherNum))
}
#funWithReturn
echo "输入的两个数字之和为 $? !"
# 函数返回值在调用该函数后通过 $? 来获得。
#注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

# 2.带参函数
# 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...
funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
# 注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

:<<EOF
参数处理    说明
$#  传递到脚本的参数个数
$*  以一个单字符串显示所有向脚本传递的参数
$$  脚本运行的当前进程ID号
$!  后台运行的最后一个进程的ID号
$@  与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-  显示Shell使用的当前选项,与set命令功能相同。
$?  显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
EOF


# ================== 文件包含 ==================
# 语法
:<<EOF
. filename   # 注意点号(.)和文件名中间有一空格
或
source filename
EOF
# 在同级目录创建test1.sh
# 引用test1.sh
. ./test1.sh
echo "网址为:$url"

本文参考菜鸟教程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容