Shell与Bash


上图从某度上找的,可以大概说明些情况。我们都知道,linux主要是靠命令来进行生产控制的,而Shell在整个生产控制过程中充当翻译的角色,将我们输入的高级语言转换成机器可以识别的二进制命令执行。shell是一个提供了命令行输入界面的程序,而通过shell程序执行shell脚本不仅可以将一条条单个命令通过逻辑控制(if、for等)高效重复地运行输出,还可以在其中调用其它程序(如convert、awk、grep等)。为了适应不同系统和机器,后期衍生出了众多的shell版本,而Bash只是众多Shell中的其中一种。为了使脚本的可移植性或兼容性,通常应在文件开头声明,如:#!/bin/bash。而.sh文件是一种标准或规范,#!/bin/sh的声明显然太过宽泛,但一般符合POSIX标准规范的基本可以兼容运行。


然而,我的 Ubuntu 下显示默认/bin/sh直接指向dash,这是因为bash过于复杂和庞大,后期版本中逐渐被dash(从原bash中抽取出必要的,作为轻量化来使用)取代,但相对而言在脚本的写法上没有bash丰富。

@lenovo ➜ ~  file -h /bin/sh 
/bin/sh: symbolic link to dash

变量

  • 名称大小写随意
  • =等号两边不能有空格
  • 取值时在名称前加 $ (在命令行中亦可生效),赋值不用
  • 运行命令前会先将变量替换为具体值
  • 命令行模式下可以传变量值,第一个值传给脚本文件中的$1,第二个值传给脚本文件中的$2,以此类推
  • 在脚本中可以写的命令同样适用于在命令行中
  • ‘单引号’ 意为里面都是作为字符串的,而 “双引号” 则可以在里面夹杂字符串和变量(注意带$)
  • 可以将命令输出的东西直接赋值,例如:var=$(ls /etc | wc -l)
特殊变量
系统变量描述
$0Bash 脚本的名称
$1 - $9Bash 脚本的前9个参数
$#多少个参数传递给 Bash 脚本
$@提供给 Bash 脚本的所有参数
$?最近运行的进程的退出状态
$$当前脚本的进程 ID
$USER运行脚本的用户的用户名
$HOSTNAME运行脚本的计算机的主机名
$SECONDS自脚本启动以来的秒数
$RANDOM每次引用时返回一个不同的随机数
$LINENO返回 Bash 脚本中的当前行
导出变量
  • script1.sh
#!/bin/zsh
var1=葡萄
var2=红枣
# 验证变量值
echo $0 :: var1 : $var1, var2 : $var2
# 将当前线程运行中的 var1 变量导入到第二个脚本中
export var1
bash ./script2.sh
# 验证导入其它变量之后的变量值
echo $0 :: var1 : $var1, var2 : $var2
  • script2.sh

    #!/bin/zsh
    # 验证变量值
    echo $0 :: var1 : $var1, var2 : $var2
    # 改变这些变量
    var1=酸奶
    var2=玉米
[Running] /bin/zsh "/home/livejq/文档/script1.sh"
/home/livejq/文档/script1.sh :: var1 : 葡萄, var2 : 红枣
./script2.sh :: var1 : 葡萄, var2 :
/home/livejq/文档/script1.sh :: var1 : 葡萄, var2 : 红枣

[Done] exited with code=0 in 0.02 seconds

运算

let 函数
# let 是 bash 的其中一个函数
# 当没有双引号时,运算过程中不允许有空格
let a=5+4
echo $a # 9
let "a = $a + 4"
echo $a # 13
let ++a
echo $a # 14
let "a = 4 * 5"
echo $a # 20
let b=5\*4
echo $b # 20
let "a = $1 + 30"
echo $a # 30 + 命令行 第一个参数
expr 函数
# 操作数和运算符之间必须要有空格
expr 5 + 4
expr "5 + 4"
expr 5+4
expr 5  \* $1
expr 11 % 2
a=$( expr 10 - 3 )
echo $a # 7

发现了没有,跟上面的 let 不同,expr 只是将运算后的结果输出,而要想跟 let 一样具有赋值功能,则可以搭配$( )

输出

@lenovo ➜ 文档  bash ./Bash.sh 2
9
5 + 4
5+4
10
1
7
@lenovo ➜ 文档
操作运算结果
+,-,\ *,/加,减,乘,除
var++与++var相当于var=var+1
var–与–var相当于var=var-1
除后取余
  • 符号在前
a=$( expr 10 - 3 )
echo $a # 7
let b=++a/8
echo $b # 1
echo $a # 8
  • 符号在后
a=$( expr 10 - 3 )
echo $a # 7
let b=a++/8
echo $b # 0 只取整数部分
echo $a # 8

双括号

a=$(( 4 + 5 ))
echo $a # 9
a=$((3+5))
echo $a # 8
b=$(( a + 3 ))
echo $b # 11
b=$(( $a + 4 ))
echo $b # 12
(( b++ ))
echo $b # 13
(( b += 3 ))
echo $b # 16
a=$(( 4 * 5 ))
echo $a # 20

赋值时需要在双括号前加 $ ,双括号中可以不空格(但为了美观,还是有必要空一下的)

变量长度

a='Hello World'
echo ${#a} # 11
b=4953
echo ${#b} # 4

IF 语句

#!/bin/zsh
# 注意缩进,增强可读性
if [ $1 -gt 100 ]
then
    echo "太大了!"
    if (( $1 % 2 == 0 ))
        then
        echo "你给的是一个偶数"
        fi
elif [ $2 == 'yes' ]
then
    echo "你输入的是 yes"
else
    echo "你没有输入 yes"
fi
pwd

不加双引号对于输出变量值比较方便,而输出字符串如果不加双引号的话需要注意转义字符

[ $1 -gt 100 ] 这对中括号是对此命令的一个测试,在命令行上等价于 test 10 -gt 100,随即可以键入 echo $? 以查看结果:0 为 true,1 为 false 。双括号是对运算表达式的检查,可以作为变量赋值$(( )))。fi 是 if 的倒写,代表 if 整个语句的结束

输出

@lenovo ➜ 文档  bash ./Bash.sh 120
太大了!
你给的是一个偶数
/home/livejq/文档

@lenovo ➜ 文档  bash ./Bash.sh 90 yes 
你输入的是 yes
/home/livejq/文档
运算符
  • 单目
操作符为 true 则执行 then~fi 代码块
! value取反
-n stringstring 字符不为空
-z stringstring 字符为空
-d FILE文件存在且是一个目录
-e FILE文件存在
-s FILE文件存在且不为空
-r FILE文件存在且有读取权限
-w FILE文件存在且有写入取权限
-x FILE文件存在且有执行权限
  • 双目
操作符为 true 则执行 then~fi 代码块
string = string字符串匹配
string != string字符串不匹配
integer -eq integer2等于( equals )
integer -gt integer2大于( greate then )
integer -lt integer2小于( less then )
integer -ge integer2大于或等于( greate or equals )
integer -le integer2小于或等于( less or equals )
xx && xx且(两者都为 true 才为真),与 -a 类似
且、或
#!/bin/zsh
# 注意缩进,增强可读性
a=5
b=200
if [[ $a -lt 10 && $b -gt 100 ]]
then
    echo "It's true"
else
    echo "It's false"
fi

if [ $a -lt 10 -a $b -gt 100 ]
then
    echo "It's true"
else
    echo "It's false"
fi

注意:[ ] 括号旁边需要至少空一格,内部操作符两边也要空格,否则会出现 bad pattern 的异常

输出

[Running] /bin/zsh "/home/livejq/文档/Bash.sh"
It's true
It's true

[Done] exited with code=0 in 0.014 seconds

Case 语句

用于替代频繁 if/else ,简化语句

#!/bin/zsh
# 注意缩进,增强可读性
case $1 in
start)
echo starting
;;
stop)
echo stoping
;;
restart)
echo restarting
;;
*)
echo don\'t know
;;
esac

)代表匹配结束,;; 代表一组模式的结束,esac 是 case 的倒写,代表 case 整个语句的结束,* 代表匹配任意情况(与 java 中的 default 类似)

输出

@lenovo ➜ 文档  bash ./Bash.sh start 
starting
@lenovo ➜ 文档  bash ./Bash.sh restart
restarting
@lenovo ➜ 文档  bash ./Bash.sh all    
don't know
@lenovo ➜ 文档  
case 高级用法
# 搭配通配符使用
space_free=$( df -h | awk '{ print $5 }' | sort -n | tail -n 1 | sed 's/%//' )
case $space_free in
[1-5]*)
echo 磁盘空间充裕
;;
[6-7]*)
echo 磁盘空间在未来将会出现瓶颈
;;
8*)
echo 你可能需要清理掉一些垃圾来增加磁盘容量
;;
9*)
echo 磁盘容量不足,电脑正面临崩溃!
;;
*)
echo 没有匹配项
;;
esac

循环 while、until 和 for

counter=1
while [ $counter -le 10 ]
do
echo $counter
((counter++))
done
echo 输出完毕

until [ $counter -gt 10 ]
do
echo $counter
((counter++))
done
echo 输出完毕

for value in {1..10}
do
echo $value
done
echo 输出完毕

输出结果相同

[Running] /bin/zsh "/home/livejq/文档/Bash.sh"
1
2
3
4
5
6
7
8
9
10
输出完毕

[Done] exited with code=0 in 0.023 seconds
for 的其它额外用法
# 用默认的空格作为分隔符构造一个字符串列表
sports='篮球 足球 羽毛球'
for sport in $sports
do
echo $sport
done
echo 输出完毕

输出

[Running] /bin/zsh "/home/livejq/文档/Bash.sh"
篮球 足球 羽毛球
输出完毕

[Done] exited with code=0 in 0.023 seconds
for value in {10..0..2}
do
echo $value
done
echo 输出完毕

输出

[Running] /bin/zsh "/home/livejq/文档/Bash.sh"
10
8
6
4
2
0
输出完毕

[Done] exited with code=0 in 0.014 seconds
for 的高级用法
# 搭配通配符使用
for value in $1/*.html
do
cp $value $1/$( basename -s .html $value ).php
done
echo "已将某个目录下的所有 html 后缀的文件转换为 php 后缀(保留源文件)"

for 跟 java 一样有 break 和 continue,用法类似

select 语句

menu='纯净水 啤酒 可乐 果汁 不需要'
# 更改系统变量 PS3 的值,以便将提示设置为更具描述性的内容(默认为#?)
PS3='先生,请问您需要喝点什么呢: '
select drink in $menu
do
    if [ $drink = '不需要' ]
        then
        break
    fi
echo 先生,您的 $drink
done
echo "随时为您服务!"

输出

@lenovo ➜ 文档  bash ./Bash.sh
1) 纯净水
2) 啤酒
3) 可乐
4) 果汁
5) 不需要
先生,请问您需要喝点什么呢: 2
先生,您的 啤酒
先生,请问您需要喝点什么呢: 5
随时为您服务!
@lenovo ➜ 文档  

括号总结

类型描述
$( )执行命令
$(( ))将算术运算后的结果赋值
(( ))仅仅执行算术运算
[ ]括号旁边需要空格,返回真假
[[ ]]用于添加且、或,同样返回真假
${ }${var}与$var一样可以的取值,只是${ }可以对变量作更加丰富的操作,如${#var}输出 var 长度
( )返回数组

学完这些,写个基本的 Bash 脚本基本没问题了 😌

参考资料:

  1. Ryans Tutorials
  2. bash shell 中如何区别 $()和${}和$(())和(())
  3. Is bash scripting the same as shell scripting?
  4. linux的命令行操作和shell的区别
  5. Shell也叫做命令行界面
  6. Shell简介
  7. difference-between-sh-and-bash
  8. bash, sh, dash 傻傻分不清楚
留言评论
推荐阅读
  • 最新图解并详细说明GitHub中12条许可证

    概述这12条开源协议/许可证都包含(相当于Unlicense): 免责声明:即作者不承担使用后所造成的任何后果,不提供资料的可用性...

    最新图解并详细说明GitHub中12条许可证
  • README文件中的徽标该如何使用

    自定义徽标徽标由左右两块方框所组成,左边显示标签,右边显示该标签所要表达的信息。 终端模式终端模式是真正的让用户自定义徽标,只需用户提...

    README文件中的徽标该如何使用
  • 规范书写GitHub中的README

    记住:是文档,而不是代码,定义了这是一个什么项目。 概述 编写并规范README自述文件的好处 节约你我他的时间...

    规范书写GitHub中的README