-Shell 教程 Bash 脚本 基础语法 MD

余生长醉 提交于 2020-03-10 18:03:37
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

Shell 简介

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言

Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务

Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell

Shell 脚本

Shell 脚本(shell script),是一种为 shell 编写的脚本程序。

业界所说的 shell 通常都是指 shell 脚本,但读者朋友要知道,shellshell script 是两个不同的概念。

由于习惯的原因,简洁起见,本文出现的 shell编程 都是指 shell 脚本编程,不是指开发 shell 自身。

Shell 环境

Shell 编程跟 JavaScript、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)

本教程关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell

在一般情况下,人们并不区分 Bourne ShellBourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash

#! 用于告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

第一个shell脚本

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

输入一些代码,第一行一般是这样:

实例

#!/bin/bash
# 使用哪一种 Shell,可以省略。不能在 #! 的行末添加注释
echo "Hello World !" # 向窗口输出文本
  • #! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。这一行一般也是可以省略的。
  • echo 命令用于向窗口输出文本。

运行 Shell 脚本有两种方法:

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/sh ./test.sh】
/bin/php test.php

这种方式运行的脚本,不需要在第一行指定解释器信息(写了也没用,因为会以运行时指定的解释器为准)。

Shell 变量

定义变量

定义变量时,变量名不加美元符号$,如:

your_name="runoob.com"

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样!

除了显式地直接赋值,还可以用语句给变量赋值,如:

for file in `ls /etc` #注意,这里用的是反斜杠而不是单引号,表示的是执行结果
    do printf  "当前目录的文件:"
    echo $file
done
for file in `ls /etc`; do echo 当前目录的文件:$file; done  #注意,如果换行了可以省略分号,否则不能省略
for file in $(ls /etc); do echo $file; done

以上语句将 /etc 下目录的文件名循环出来。

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

your_name="qinjx"
echo $your_name
echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

for skill in Ada Coffe Action Java; do
    echo "I am good at ${skill}Script"
done
I am good at AdaScript
I am good at CoffeScript
I am good at ActionScript
I am good at JavaScript

如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

推荐给所有变量加上花括号,这是个好的编程习惯。

已定义的变量,可以被重新定义,如:

your_name="tom"
echo $your_name
your_name="alibaba"
echo $your_name

这样写是合法的,但注意,第二次赋值的时候不能加美元符,使用变量的时候才加美元符

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变

readonly myUrl="http://www.google.com"
myUrl="http://www.google.com"
readonly myUrl

下面的例子尝试更改只读变量,结果报错:

readonly myUrl="http://www.google.com"
myUrl="http://www.runoob.com" #报错 ./test.sh: line 2: myUrl: readonly variable

删除变量

使用 unset 命令可以删除变量。语法:

unset variable_name

变量被删除后不能再次使用。unset 命令不能删除只读变量

myUrl="http://www.runoob.com"
unset myUrl
echo $myUrl #不报错,但没有任何输出
readonly myUrl="http://www.runoob.com"
unset myUrl #报错./test.sh: line 2: unset: myUrl: cannot unset: readonly variable

Shell 字符串

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。

单引号

str='this is a string'

单引号字符串的限制:

  • 单引号里的任何字符都会【原样输出】,单引号字符串中的变量是无效的
  • 单引号字串中不能出现单独一个的单引号,对单引号使用转义符后也不行()
  • 单引号字串中可成对出现一对单引号,作为字符串拼接使用
name=白乾涛
test1='hello, ${name}sir!'    #不加符号,原样输出,hello, ${name}sir!
test2='hello, `$name`sir!'    #加反斜杠,原样输出,hello, `$name`sir!
test3='hello, "$name"sir!'    #加双引号,原样输出,hello, "$name"sir!
test4='hello, '$name'sir!'    #加单引号,正常输出,hello, 白乾涛sir!
echo $test1 $test2 $test3 $test4

双引号

双引号里可以有变量,可以出现转义字符

name=白乾涛
test1="hello, ${name}sir!"      #不加符号,hello, 白乾涛sir!
test2="hello, \`$name\`sir!"   #加反斜杠且转义,hello, `白乾涛`sir!
test3="hello, "$name"sir!"     #加双引号,hello, 白乾涛sir!
test4="hello, \"$name\"sir!"  #加双引号且转义,hello, "白乾涛"sir!
test5="hello, '$name'sir!"      #加单引号,hello, '白乾涛'sir!
test6="hello, \'$name\'sir!"   #加单引号且转义,hello, \'白乾涛\'sir!
echo $test1 $test2 $test3 $test4 $test5 $test6

echo "hello, `$name`sir!"      #反斜杠必须转义,否则报错./test.sh: line 10: 白乾涛: command not found

字符串基本操作

string="baiqiantao"
echo "字符串长度:${#string}" #字符串长度:4
echo "子串为:${string:3:4}" #从第 index 个字符开始截取 length 个字符。子串为:qian
echo "字符位置:`expr index "$string" qt`"  # 字符位置:4(index+1)
# 查找字符 q 或 t 的位置(哪个字母先出现就计算哪个),以上是反引号 `,而不是单引号 '

Shell 数组

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

类似于 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。

定义数组

在 Shell 中,用小括号来表示数组,数组元素用空格或换行符号分割开,初始化时不需要定义数组大小。

定义数组的一般形式为:

数组名=(值1 值2 ... 值n)
array=(A 2 "C"
4.3
'5')

还可以单独定义数组的各个分量,可以不使用连续的下标,而且下标的范围没有限制:

array[1]=1
array[5]=5
array[n]=白乾涛 #这里的n代表某一个整数,如果未定义直接使用,代表0

数组的基本操作

基本操作

  • 获取数组所有元素:${array_name[*]}${array_name[@]}
  • 获取数组某个元素的值:${array_name[index]},index是从0开始的
  • 获取数组的长度:${#array_name[*]}${#array_name[@]}
  • 获取数组某个元素的长度:${#array_name[index]}
array=(A 2 "C" 4.3 '5')
array[8]=8
array[n]=白乾涛 #这里的n代表某一个整数,如果未定义直接使用,代表0

echo "数组的元素为: ${array[*]}" #白乾涛 2 C 4.3 5 8
echo "第一个元素为: ${array[0]}" #白乾涛
echo "数组的长度为:${#array[*]}" #6
echo "第一个元素长度为: ${#array[0]}" #3

Shell 注释

# 开头的就是注释,会被解释器忽略。
如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。

多行注释还可以使用以下格式:

:<<EOF
注释内容...
EOF

EOF 也可以使用其他符号。

Shell 传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n,n 代表第 n 个参数

echo "你的性别:$2";
echo "你的年龄:$1";

执行脚本:

./test.sh 29 男
你的性别:男
你的年龄:29

特殊字符处理参数

有几个特殊字符用来处理参数:

符号 说明
$0 执行的文件名
$# 传递到脚本的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$@ $*相同,但是使用时加引号,并在引号中返回每个参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
echo "你的年龄:$1";
echo -e  "你的性别:$2\n";

echo "执行的文件名:$0";
echo -e "参数个数为:$#\n";

echo "传递的参数作为一个字符串显示:$*";
echo -e "传递的参数作为一个字符串显示:$@\n";

echo "脚本运行的当前进程ID号:$$";
echo "后台运行的最后一个进程的ID号 :$!";
echo "显示Shell使用的当前选项:$-";
echo "显示最后命令的退出状态:$?";

执行脚本,输出结果如下所示:

./test.sh 29 男
你的年龄:29
你的性别:男

执行的文件名:./test.sh
参数个数为:2

传递的参数作为一个字符串显示:29 男
传递的参数作为一个字符串显示:29 男

脚本运行的当前进程ID号:14372
后台运行的最后一个进程的ID号 :
显示Shell使用的当前选项:hB
显示最后命令的退出状态:0

$*$@ 的异同点

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

相同点:都是引用所有参数

for i in $*; do # 【$*】、【$@】、【"$@"】的结果是一样的
    echo $i
done
./test.sh 29 男 白乾涛
29
男
白乾涛

不同点:在双引号中,"$*"的作用是将所有参数当做一个参数

for i in "$*"; do
    echo $i
done
./test.sh 29 男 白乾涛
29 男 白乾涛

Shell 基本运算符

Shell 和其他编程语言一样,支持多种运算符,包括:

  • 算数运算符
  • 关系运算符
  • 布尔运算符
  • 字符串运算符
  • 文件测试运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awkexpr。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

val=`expr 2 + 2`
echo "两数之和为 : $val" #两数之和为 : 4

两点注意:

  • 表达式和运算符之间必须要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样
  • 完整的表达式要被反引号包含,注意这个字符不是常用的单引号

算术运算符

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
% 取余
= 赋值
== 相等
!= 不相等

注意:

  • 乘号*前边必须加反斜杠\才能实现乘法运算
  • 在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 *不需要转义符号\

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明
-eq 检测两个数是否相等
-ne 检测两个数是否不相等
-gt 检测左边的数是否大于右边的
-lt 检测左边的数是否小于右边的
-ge 检测左边的数是否大于等于右边的
-le 检测左边的数是否小于等于右边的

布尔运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明
! 非运算
-o 或运算,or
-a 与运算,and

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

  • && 逻辑与 [[ $a -lt 100 && $b -gt 100 ]] 返回 false
  • || 逻辑或 [[ $a -lt 100 || $b -gt 100 ]] 返回 true

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":

运算符 说明
= 检测两个字符串是否相等
!= 检测两个字符串是否不相等
-z 检测字符串长度是否为0
-n 检测字符串长度是否不为0
$ 检测字符串是否不为空

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

操作符 说明
-b file 检测文件是否是块设备文件
-c file 检测文件是否是字符设备文件
-d file 检测文件是否是目录
-f file 检测文件是否是普通文件既不是目录,也不是设备文件
-g file 检测文件是否设置了 SGID 位
-k file 检测文件是否设置了粘着位(Sticky Bit)
-p file 检测文件是否是有名管道
-u file 检测文件是否设置了 SUID 位
-r file 检测文件是否可读
-w file 检测文件是否可写
-x file 检测文件是否可执行
-s file 检测文件是否为空(文件大小是否大于0)
-e file 检测文件(包括目录)是否存在

其他检查符:

  • -S: 判断某文件是否 socket
  • -L: 检测文件是否存在并且是一个符号链接

Shell echo 命令

Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出。
echo 会自动添加换行符。

echo "It is a test" #显示普通字符串,每输出一次会自动换行
echo It is a test    #双引号完全可以省略
echo "\"It is a test\""  #显示转义字符【"It is a test"】,双引号同样也可以省略
name=白乾涛
echo "$name It is a test" #显示变量,双引号同样也可以省略
echo -e "It is a test\n" #显示换行,【-e】为开启转义,【\n】表示换行
echo -e "It is a test\c" #显示不换行,【\c】表示不换行,【\c】后面的内容会丢弃
echo '---$name\"' #用单引号可以原样输出字符串,不进行转义或取变量
echo `date` #使用反引号可以显示命令执行的结果
It is a test
It is a test
"It is a test"
白乾涛 It is a test
It is a test

It is a test---$name\"
2019年11月 4日 0:05:17

Shell printf 命令

printf 命令模仿 C 程序库里的 printf() 程序。
printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好
printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。
默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。

printf 命令的语法:

printf format-string [arguments...] #格式控制字符串 为参数列表

格式替代符

%s%c%d%f都是格式替代符,分别代表替换字符串、字符、整数、浮点数,例如:

printf "%-9s %-6s %5s\n" 姓名 性别 体重kg
printf "%-9s %-3s %5.2f\n" 白乾涛 男 66.1
printf "%-9s %-3s %5.2f\n" 白乾涛 M 66.1
printf "%-9s %-3s %5.2f\n" 乾涛 男 6.1
printf "%-9s %-3s %5.2f\n" baiqiantao 男 666.1234
姓名    性别 体重kg   #空格数4、1
白乾涛 男 66.10     #空格数1、1
白乾涛 M   66.10       #空格数1、3
乾涛    男  6.10       #空格数4、2
qiantao   男 666.12  #空格数3、1

其中:

  • %-9s 指宽度为9个字符(一个中文算3个字符、一个英文算一个字符),如果宽度不足则自动以空格填充超过也会将内容全部显示出来。其实可以理解为最少显示n个字符。
  • -表示左对齐,没有则表示右对齐。
  • %-5.2f 指格式化为小数,其中5代表(最少)显示几个字符(小数点也算在内),.2指(强制)保留2位小数。

使用案例

基本案例

printf "年龄:30\n"     #可以省略参数列表(此时即使有参数列表也是无效的)【年龄:30】
printf "年龄:%d\n" 30  #双引号【年龄:30】
printf '年龄:%d\n' 30  #单引号,与双引号效果一样
printf 年龄:%d 30      # 没有引号也可以输出

错误案例

printf %s "包青天\n" #【换行失败】有格式化字符时,参数列表中的换行符无效
printf %s\n "白乾涛" #【换行失败】格式化字符中有换行符时,需要用双引号引住

# 打印内容【包青天\n白乾涛n】

格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用

printf 姓名:%s 白乾涛 包青天 #【姓名:白乾涛姓名:包青天】
printf "姓名:%s\n" 白乾涛 包青天 #【姓名:白乾涛(换行)姓名:包青天】
printf "%s %s %s\n" a b c d e f g #每3个一个换行
姓名:白乾涛姓名:包青天姓名:白乾涛
姓名:包青天
a b c
d e f
g

如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替,%c 用空格代替,%f 用 0.000000 代替

printf "%s和 %d 和%c和 %f \n" #【和 0 和 和 0.000000】

printf 的转义序列

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

2019-9-1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!