Linux-Shell设计
Posted living_frontier
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-Shell设计相关的知识,希望对你有一定的参考价值。
一、shell 总论
shell 就是“壳程序”,这个名字是针对 kernel 来说的,也就是在操作系统外围的程序(严格的讲,已经不是操作系统了)。宏观上的 shell 是所有的应用程序,而狭义上的 shell,指的是命令行方面的操作系统界面。我们通过 shell 将我们输入的命令与内核沟通,好让内核可以控制硬件来正确无误地工作。
我们在终端上经常敲的 ls
、cd
等命令,其实都是一组一组得引用程序(存放在 \\bin
) 里,我们通过 shell 调用这些程序来执行我们的指令。
bash 是 shell 的一种,linux 上还可以有多种 shell 程序。
关于 shell 本身的使用,只有两点需要强调,就是如果一个命令过长,那么可以使用反斜杠 \\
进行换行输出。此外,如果觉得输入命令太麻烦,可以使用别名设置,比如强大需求的 ll
。
alias="ls -al"
二、变量
2.1 使用、声明与取消
变量的使用用的是如下格式
echo $name
echo $name
其中 echo
是在控制台界面打印变量的意思,$
是引用内容的意思。
bash 的赋值变量有点像弱数据类型语言的赋值,赋值的时候可以不指定类型(默认字符串)。赋值的时候,需要注意整个语句一般是不能有空格的,所以只有下面这种是对的:
name=Thysrael
然后以下的都是错误的
name = Thysrael
name=Thysrael I miss you
如果,真的想要进行有空格的赋值,那么应该采用双引号或者单引号的形式,两者的区别如下
name=Thysrael
sayhi="$name I miss you"
sayhello='$name I miss you'
可以看出,在双引号里的变量会有进行内容引用,而当引号内只是单纯的字符串。
按照这样的声明方法声明出的变量,都是字符串类型的(但是似乎在 bash 上,字符串类型就已经有很强大的功能了)。我们还可以采用如下命令来声明其他类型的变量:
declare [-aixr] variable_name
其中参数的意义如下表:
参数 | 意义 |
---|---|
-a | 数组 |
-i | 整型 |
-x | 设置为环境变量 |
-r | 即 readonly ,不能被更改,也不能被 unset |
累加内容的写法有两种:
name="$name"Shaw
name=$nameShaw
如果想要取消变量,有如下写法
unset name
2.2 作用范围
变量的作用范围分为三种,环境变量、全局变量、局部变量。局部变量是指在脚本函数体中声明的变量,其作用域只有函数体内,因为脚本的知识是在后面涉及的,所有就不在此处介绍了。
全局变量就是像上面 name
这样变量,他的作用范围是整个进程(也就是 shell 程序的一个“实例化”)。如果在 bash 里面输入:
bash
就可以开启一个子进程,(虽然不会开启一个新的终端,但是确实是启动了一个新进程)
$
是一个环境变量(应该是吧),其内容是当前的进程号(PID),我们可以用命令 echo $$
打印来看看我们是否重新创建了一个进程,来佐证我们的观点,此外,还需要知道该命令
exit
这个命令是退出当前进程。
具体呈现的就是这样的结构:
所谓的全局变量,就是只能在当前进程中起作用。比如下面的例子:
只能在父进程里面打印出 name
变量,而在子进程中并没有 name
变量。
而环境变量指的就是在父进程和其所有衍生出的进程里都可以使用的变量。我们可以用 export
或者 env
命令进行查看。
如果我们想把一个全局变量变成环境变量,可以使用如下命令:
export variable
可以发现,是可以在子进程中使用的。但是需要强调的是,这个变量在其他无关进程中依然是没法使用的,比如说我新开一个终端,那么会发现之前终端上定义的各种变量都消失了。
如果想要定义的变量在每个终端中都预定义,那么就必须把这种变量写到 bash 的配置文件中,这样的变量一般都是大写的。
2.3 读取
采用如下命令
read variable_name
输入这个命令以后,bash 会等待输入,为了提示用户进行交互,还可以添加提示字符参数:
read -p "Please input the content of this variable: " variable_name
2.4 删除
这里说的删除是指删除字符串变量的一个子串,如果想要快速删除一个子串,当然最快的方法是使用通配符(退化版的正则表达式)。删除一次只能删除一个子串,那么就是涉及两个问题:
- 从前面开始删还是从后面开始删
- 当采用通配符的时候,是贪婪模式还是非贪婪模式
首先先看一个标准的例子:
可以看到,用这种格式 path命令符/目标子串
可以起到删除目的。其中 命令符
有以下几种:
命令符 | 解释 |
---|---|
# | 采用非贪婪模式从左到右开始匹配(前缀必须匹配) |
## | 采用贪婪模式从左到右开始匹配(前缀必须匹配) |
% | 采用非贪婪模式从右到左开始匹配(后缀必须匹配) |
%% | 采用贪婪模式从右到左开始匹配(后缀必须匹配) |
实验结果如下:
2.5 空串替换
这里说的是一个很有趣的问题,对于一个字符串变量名。有三种状态:
- A:未被声明
- B:被声明,但是被声明为空串
- C:被声明,但是不为空串
有如下表格:
变量设置方式 | A | B | C |
---|---|---|---|
var=$str-expr | var=expr | var= | var=$str |
var=$str:-expr | var=expr | var=expr | var=$str |
var=$str+expr | var= | var=expr | var=expr |
var=$str:+expr | var= | var= | var=expr |
var=$str=expr | str=expr,var=expr | str不变,var= | str不变,var= |
var=$str:=expr | str=expr,var=expr | str=expr,var=expr | str不变,var= |
做了一些实验:
2.6 整数计算
因为变量的默认形式都是字符串,所以对于这种代码,会有很不尽如人意的输出
所以需要采用特定的写法,如下
还有用命令写的:
三、重定向
其实没有想象的那么难,首先有下面的图
其实这幅图只是在说明,当 bash 运行的时候,同时打开是三个文件。我们在重定向中,用 0
代表 stdin
,1
代表 stdout
,2
代表 stderr
。
当我们想要把指令的输出内容重定向的时候,可以采用如下命令
command 1> file
此时 e.txt
的内容就变成了:
如果接着运行其他重定向命令,那么就会有如下结果:
会发现原来 ll
的输出被 pwd
掩盖了。如果我们想要进入追加模式,那么应该有如下命令
command 1>> file
其次是其实 1
是可以省略的,比如:
ls >e.txt
如果想要把标准错误重定向,也是类似,如:
find /home -name .bashrc > list_right 2> list_error
如果想要标准输出和标准错误同时输入一个文件,可以采用以下写法:
find /home -name .bashrc > list_right 2> list_error 1>&2
这个指令的意思是,先将标准错误重定向到 list_error
,然后再将标准输出重定向到标准错误重定向的文件。
还有一个有意思的东西是,如果不想要输出信息了,可以考虑将输出重定向到一个特殊的文件中,这个特定的文件可以当垃圾桶
command > /dev/null
四、多条命令关系
4.1 命令执行的判断根据
如果想要一条语句执行多条命令,那么可以采用这种写法
cmd1; cmd2; cmd3
但是这种写法其实就是把三行命令写到了一起,其实没啥逻辑关系。可是如果是这种
cmd1 && cmd2
这个语句的意思是只有 cmd1
成功执行,cmd2
才能成功执行。这个的底层意思可以这样理解:首先介绍一个变量(可能是个变量吧):?
。这个的意思是上一条指令的执行结果如何,如果执行成功,则有 $? = 0
。这条语句的意思就是当 cmd1
的 $?=0
的时候,才执行 cmd2
。最后实现的效果就是,cmd_n
会按照顺序一条一条的执行,直到执行完成,或者有一个指令报错。
与之相对的,有如下命令
cmd1 || cmd2
意思是当第一条指令执行错误的时候,第二条指令才会执行,这个已看上去会有点难以理解,不过其实这句话说得有点类似于一个 trycatch
结构。
4.2 管道命令
管道命令的意思就是通过连接符 |
,使前一条指令的标准输出作为后一条指令的标准输入。下面这张图很好的描述了这个概念:
但是可以接受 stdin
的命令并不多,我们进行以下总结:
但是其实能接受 stdin
的命令本来就不多,这里介绍几个用法:
4.2.1 阅读指令
这两个指令其实不应该在这里讲,这个应该属于是在文件那里讲的,这两个命令与 cat
类似,都是用于阅读文本文件的(vim
与他们的区别在于不仅可以阅读,还可以修改)。more
和 less
都是按页阅读文件,也就是说,当文本文件的内容过多的时候,用 cat
就没有办法显示全了(只会显示一个屏幕,想看上面的必须用滚轮去捯)。这时就引入了 more
和 less
。我将他们理解为一种文本阅读器,这么说的理由是他们不止有阅读的功能,还有查找,光标移动等功能。此外,more
可以看成 less
功能的一个子集,more
的功能更少,而 less
更多一些,所以我在日常中使用 less
。不存在权衡的问题。
演示如下,输入命令:
less /etc/debconf.conf
可以看到如下场景:
这个界面其实是很像 vim
的界面的。而且实际上也很像,有如下操作
Space
:向下翻一页Enter
:向下滑一行/查找内容
:向下寻找内容?查找内容
:向上寻找内容n
:向下重复上次的查找操作N
:向上重复上次的查找操作(上面这四条查找相关,都是与 vim 中相同)q
:退出(也可以是ZZ
,这也是与 vim 相同的地方)g
:到文本的第一行去G
:到文本的最后一行去
正是因为其强大的阅读器功能,less
常常作为管道命令的接受者,即阅读某个命令的输出信息,有如下示例:
可以看到,我把我的博客的 generate 的输出信息输入到了 less
中,这样我就可以用 less
来查看我的产生信息了。
4.2.2 选取指令
4.2.2.1 head tail
这里介绍四种命令,第一个是 head
,用来截取信息的前面的几行,如下示例:
head -n [num]
就是显示头部 num 行
第二个是 tail
就是显示尾部信息
tail -n [num]
4.2.2.2 grep
第三个是 grep
如果说前面查找行是按照行号,grep
则是依靠查找内容来进行行的检索,其格式如下
grep [-acinv] '查找字符串' filename
其中参数有:
-a
:将二进制文件按照文本文件的方式查询-c
:计算找到查找字符的次数-i
:忽略大小写的不同-n
:输出查找内容的行号-v
:反向选择,即输出没有查找字符的行
举例如下:
比较高阶一点的用法是将查找字符串用正则表达式的形式表示,这样的话,查找会更加快一些。
4.2.2.3 sed
sed
是一个按行处理文本的工具,前三个还比较局限于检索功能,而 sed
的编辑功能是强大的。其本质如下
sed 是一种流编辑器,他是文本处理中的工具,能够完美配合正则表达式使用。处理时,把当前处理的行存储在临时缓存区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区中的内容送往屏幕。接着处理下一行,这样不断重复,直至文件结尾。
其实需要注意的是,就是 sed
不会修改目标文件的内容,而只是将改变后的信息输出到屏幕上。
sed
的命令由三部分组成(如果用作管道命令的话,就不需要后面的文件了),即
sed [参数] '查找1操作1;查找2操作2;查找3操作3...' filename
参数如下
字符 | 含义 |
---|---|
-n | 沉默模式,仅显示处理后的结果,一般打印的时候都需要开着 |
-i | 直接修改文件内容,而不是由屏幕输出 |
-r | 使用扩展正则表达式的语法 |
其他 | 不重要,就不总结了 |
这是开不开沉寂模式的对比
查找的方法有很多:
- 按行数查找
- 一个特定的行数:
1
表示第一行 - 一个范围的行数:
1,3
表示第 1,2,3 行 - 一个范围的行数,到结尾:
2,$
表示从第 2 行开始到结尾行
- 一个特定的行数:
- 正则表达式查找:
/正则表达式/
。似乎正则有一些是没有办法使用的,示例如下
操作的功能也有很多,如下表
符号 | 解释 |
---|---|
d | 删除 |
a | 追加,会追加到搜索到的下一行 |
p | 打印,就是把搜索到的内容打印出来(以行为单位) |
i | 插入,会插入到搜索到的上一行 |
s | 替代,/old/new ,但是只会找一个,如果想要全局替代,需要写成 /old/new/g |
c | 取代,会把范围内的行全都替换掉 |
4.2.2.4 awk
相较于 sed
常常用作于一个整行的数据处理,awk
则比较倾向于一行当中分成数个字段来处理。
其基本的处理模式如下:
awk '条件类型1操作1 条件类型2操作2...' filename
awk
会默认按空格将一行中的内容分割成多个字段,我们可以用字段变量来表示获得的字段。其中 $0
代表一整行数据,$1
代表第一个个字段,$2
代表第二个字段,以此类推。
此外,awk
还有许多内置变量,用以更好的完成工作
变量 | 意义 |
---|---|
NF | 每一行拥有的字段总数 |
NR | 目前 awk 所处理的是第几行数据 |
FS | 分割字符,可以通过设置它来分割字符 |
前两个变量常常用于构建条件,如下,就是把偶数行取了出来,可以看到这种变量使用的时候,不需要使用 $
第三个可以用于指定分隔符,可以把 BEGIN
看成一种特殊的条件
4.2.2.5 cut
cut
是弱化版的 awk
实现的是行内信息的切割,有如下格式,-d
说明要声明分割字符,-f
说明要挑选字段
cut -d '分割字符' -f num1,num2
这条命令表示按照分割字符进行切分,并将切分的结构构成的子串,挑选出第num1到第num2个子串输出。
还有另外一种格式,即:
cut -c num1-num2
意思是挑选每一行从第num1到第num2个字符进行输出。效果如图:
五、脚本
5.1 解释器
这里想谈一下我对解释型语言和编译型语言的理解。我们常说解释型语言写出来的源码叫做脚本。我觉得二者的区别在于是否经过编译。对于脚本语言(比如python,java,matlab),他们是不直接编译生成一个可执行文件的,他们会将源码交到一个叫做解释器的程序手里,由解释器读取相关的信息,并执行相关的操作。这个解释器在python里叫python解释器,在java里叫java虚拟机。如果说的再彻底一点,可以说解释器是脚本和操作系统之间的一个新的抽象层,就好像一个功能不太健全的虚拟机一样。而编译型语言并没有这个抽象的中间层,它直接编译生成可执行文件,这个文件可以直接运行,而不是通过解释器。
shell 脚本就是发挥了 shell 作为一个解释器的功能,开发出的很多个程序。
shell 脚本第一行会有一个固定的写法,来声明所用的 shell 程序,被叫做 shebang 行,如
#!/bin/bash
5.2 基础
脚本一般后缀名为 .sh
。想要运行脚本,可以使用命令
bash sh_name.sh
脚本中经常出现分号 ;
。在实践的过程中,我发现去掉几个也没有啥关系(主要是不去真的很丑),然后发现网上有一个结论是 “如果写成单行,需要用分号进行区分,如果写成块,那么则用换行符替代了分号”。不知道对不对。
5.3 条件判断
条件判断有两种格式,但是都是一种条件判断的格式,表格如下
判断文件类型,其格式为(注意 [\\space\\space]
必须与里面的内容有两个空格的间隔 )
test -arg filename
[ -arg filename ]
参数(arg) | 意义 |
---|---|
-e | 该文件名是否存在 |
-f | 该文件名是否是文件 |
-d | 该文件名是否是目录 |
-S | 该文件是否是一个 socket 文件 |
-L | 该文件名是否是一个链接文件 |
判断文件权限,格式如下
test -arg filename
[ -arg filename ]
参数(arg) | 意义 |
---|---|
-r | 判断该文件名是否具有可读性 |
-w | 判断该文件名是否具有可写性 |
-x | 判断该文件名是否具有可执行性 |
比较两文件,格式如下
test file1 -arg file2
[ file1 -arg file2 ]
参数(arg) | 意义 |
---|---|
-nt | newer than 判断 file1 是否比 file2 新 |
-ot | older than 判断 file1 是否比 file2 旧 |
-ef | 判断 file1 与 file2 是否是同一文件,判定文件是否均指向同一个 inode |
比较两个整数,格式如下
test num1 -arg num2
[ num1 -arg num2 ]
参数(arg) | 意义 |
---|---|
-eq | equal, 两数相等 |
-ne | not equal, 两数不等 |
-gt | greater than ,n1 大于 n2 |
-lt | less than,n1 小于 n2 |
-ge | greater than or equal than |
-le | less than or equal than |
判断字符串的数据,格式如下
test -arg string
[ -arg string ]
参数 | 意义 |
---|---|
-z | zero,判断字符串是空字符串 |
-n | not zero, 判断字符串不是空字符串 |
判断两字符串是否相等,格式如下(注意空格问题)
test str1\\space==\\spacestr2
test str1\\space!=\\spacestr2
[\\spacestr1\\space==\\spacestr2\\space]
[\\spacestr1\\space!=\\spacestr2\\space]
多条件判定
其实就是逻辑判断符:
参数 | 意义 |
---|---|
-a | 即 and |
-o | 即 or |
! | 即 not |
5.4 默认变量
默认变量就是输入脚本后跟的内容,有点类似于 C 程序的 main
函数的 argv
,然后还有一些约定的变量:
$#
:表示参数的个数$@
:表示[“$1"”$2"“$3"”$4"…]
光说没意思,还是放一个脚本的代码和运行结果吧:
#!/bin/bash
echo "The script name is $0"
echo "Total parameter number is $#"
[ "s#" -lt 2 ] && echo "The number of parameter is less than 2. Stop here." && exit 0
echo "Your whole parameter is $@"
echo "The 1st parameter is $1"
echo "The 2nd parameter is $2"
5.5 条件分支语句
第一种是简单的 if
语句,有格式:
if [ condition ]; then
commands
fi
有示例:
#!/bin/bash
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "Ok, continue."
exit 0
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interupt."
exit 0
fi
echo "I don't know what your choice is'" && exit 0
第二种是 if-else
语句,格式如下
if [ condition1 ]; then
commands1
elif [ condition2 ]; then
commands2
else
commands3
fi
示例如下:
#!/bin/bash
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "Ok, continue."
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interupt."
else
echo "I don't know what your choice is"
fi
第三种是 case
语句,格式如下
case $variable_name in
"value1")
command1
;;
"value2")
command2
;;
"value3")
command3
;;
*)
command4
;;
esac
示例如下:
#!/bin/bash
case $1 in
"one")
echo "Your choice is ONE."
;;
"two")
echo "Your choice is TWO."
;;
"three")
echo "Your choice is THREE."
;;
*)
echo "Usage $0 one|two|three"
;;
esac
5.6 循环语句
跟 C 的思路挺像的,分为不定循环和固定循环。
对于不定循环,有两种写法
while [ condition ]
do
commands
done
until [ condition ]
do
commands
done
其中,until
语句的意思当 condition
为真的时候,退出循环(与· while
相反),有如下示例
#!/bin/bash
while [ "yn" != "yes" -a "yn != "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
这个脚本的意思是输入 yes 就可以停止程序,如果用 until
来写的话(相同需求),为如下写法
#!/bin/bash
while [ "yn" == "yes" -o "yn == "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
固定循环就是一般为 for
也同样有多种形式
最为常见的数值 for 循环
for (( i=1; i<=n; i=i+1 ))
do
commands
done
有如下示例:
#!/bin/bash
read -p "Please input a number, I will count for 1+2+3+...+your_input" n
s=0
for (( i=1; i<=n; i=i+1 ))
do
s=$(( Ss+si ))
done
echo "The result is $s"
还有一种形式的 for 循环,格式如下
for var in con1 con2 con3 ...
do
commands
done
var
会分别取 con1,con2,con3...
进行执行。
如果想要构造一个连续序列,可以按如下写法
for c in a..z
do
echo $c
don
此时 c 就会取遍小写字母表。
5.7 函数
函数的格式如下:
function fname()
commands
需要注意的是,不需要声明函数参数,而是直接使就好了,比如 $1
就代表第一个参数,这种使用方法是与脚本的默认变量冲突的,我们认为函数的参量会覆盖脚本的默认变量,有如下示例
#!/bin/bash
function printIt()
echo "Your choice is $1"
echo "This program will print your choice."
case $1 in
"one")
printIt 1
;;
"two")
printIt 2
;;
"three")
printIt 3
;;
*)
echo "Usage $0 one|two|three"
;;
esac
Linux-Shell
Linux-Shell
概论
概况
shell是我们通过命令行与操作系统沟通的语言。
shell脚本可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。
AC Terminal中的命令行可以看成是一个“shell脚本在逐行执行”。
Linux中常见的shell脚本有很多种,常见的有:
- Bourne Shell(
/usr/bin/sh
或/bin/sh
) - Bourne Again Shell(
/bin/bash
) - C Shell(
/usr/bin/csh
) - K Shell(
/usr/bin/ksh
) - zsh
- …
Linux系统中一般默认使用bash,所以接下来讲解bash中的语法。
文件开头需要写#! /bin/bash
,指明bash为脚本解释器。
学习技巧
不要死记硬背,遇到含糊不清的地方,可以在AC Terminal里实际运行一遍
脚本示例
新建一个test.sh
文件,内容如下:
#! /bin/bash
echo "Hello World!"
运行方式
作为可执行文件
acs@9e0ebfcd82d7:~$ chmod +x test.sh # 使脚本具有可执行权限
acs@9e0ebfcd82d7:~$ ./test.sh # 当前路径下执行
Hello World! # 脚本输出
acs@9e0ebfcd82d7:~$ /home/acs/test.sh # 绝对路径下执行
Hello World! # 脚本输出
acs@9e0ebfcd82d7:~$ ~/test.sh # 家目录路径下执行
Hello World! # 脚本输出
用解释器执行
acs@9e0ebfcd82d7:~$ bash test.sh
Hello World! # 脚本输出
注释
单行注释
每行中#
之后的内容均是注释。
# 这是一行注释
echo 'Hello World' # 这也是注释
多行注释
格式:
:<<EOF
第一行注释
第二行注释
第三行注释
EOF
其中EOF
可以换成其它任意字符串。例如:
:<<abc
第一行注释
第二行注释
第三行注释
abc
:<<!
第一行注释
第二行注释
第三行注释
!
变量
定义变量
定义变量,不需要加$
符号,例如:
name1='yxc' # 单引号定义字符串
name2="yxc" # 双引号定义字符串
name3=yxc # 也可以不加引号,同样表示字符串
使用变量
使用变量,需要加上$
符号,或者${}
符号。花括号是可选的,主要为了帮助解释器识别变量边界。
name=yxc
echo $name # 输出yxc
echo ${name} # 输出yxc
echo ${name}acwing # 输出yxcacwing
只读变量
使用readonly
或者declare
可以将变量变为只读。
name=yxc
readonly name
declare -r name # 两种写法均可
name=abc # 会报错,因为此时name只读
删除变量
unset
可以删除变量。
name=yxc
unset name
echo $name # 输出空行
变量类型
- 自定义变量(局部变量)
子进程不能访问的变量 - 环境变量(全局变量)
子进程可以访问的变量
自定义变量改成环境变量:
acs@9e0ebfcd82d7:~$ name=yxc # 定义变量
acs@9e0ebfcd82d7:~$ export name # 第一种方法
acs@9e0ebfcd82d7:~$ declare -x name # 第二种方法
环境变量改为自定义变量:
acs@9e0ebfcd82d7:~$ export name=yxc # 定义环境变量
acs@9e0ebfcd82d7:~$ declare +x name # 改为自定义变量
字符串
字符串可以用单引号,也可以用双引号,也可以不用引号。
单引号与双引号的区别:
- 单引号中的内容会原样输出,不会执行、不会取变量;
- 双引号中的内容可以执行、可以取变量;
name=yxc # 不用引号
echo 'hello, $name \\"hh\\"' # 单引号字符串,输出 hello, $name \\"hh\\"
echo "hello, $name \\"hh\\"" # 双引号字符串,输出 hello, yxc "hh"
获取字符串长度
name="yxc"
echo ${#name} # 输出3
提取子串
name="hello, yxc"
echo ${name:0:5} # 提取从0开始的5个字符
默认变量
文件参数变量
在执行shell脚本时,可以向脚本传递参数。$1
是第一个参数,$2
是第二个参数,以此类推。特殊的,$0
是文件名(包含路径)。例如:
创建文件test.sh
:
#! /bin/bash
echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "第三个参数:"$3
echo "第四个参数:"$4
然后执行该脚本:
acs@9e0ebfcd82d7:~$ chmod +x test.sh
acs@9e0ebfcd82d7:~$ ./test.sh 1 2 3 4
文件名:./test.sh
第一个参数:1
第二个参数:2
第三个参数:3
第四个参数:4
其它参数相关变量
参数 | 作用 |
---|---|
$# | 代表文件传入的参数个数,如上例中值为4 |
$* | 由所有参数构成的用空格隔开的字符串,如上例中值为"$1 $2 $3 $4" |
$@ | 每个参数分别用双引号括起来的字符串,如上例中值为"$1" "$2" "$3" "$4" |
$$ | 脚本当前运行的进程ID |
$? | 上一条命令的退出状态(注意不是stdout,而是exit code)。0表示正常退出,其他值表示错误 |
$(command) | 返回command 这条命令的stdout(可嵌套) |
command | 返回command 这条命令的stdout(不可嵌套) |
数组
数组中可以存放多个不同类型的值,只支持一维数组,初始化时不需要指明数组大小。
数组下标从0开始。
定义
数组用小括号表示,元素之间用空格隔开。例如:
array=(1 abc "def" yxc)
也可以直接定义数组中某个元素的值:
array[0]=1
array[1]=abc
array[2]="def"
array[3]=yxc
读取数组中某个元素的值
格式:
${array[index]}
例如:
array=(1 abc "def" yxc)
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
echo ${array[3]}
读取整个数组
格式:
${array[@]} # 第一种写法
${array[*]} # 第二种写法
例如:
array=(1 abc "def" yxc)
echo ${array[@]} # 第一种写法
echo ${array[*]} # 第二种写法
数组长度
类似于字符串
${#array[@]} # 第一种写法
${#array[*]} # 第二种写法
例如:
array=(1 abc "def" yxc)
echo ${#array[@]} # 第一种写法
echo ${#array[*]} # 第二种写法
expr命令
expr
命令用户求表达式的值,格式为:
expr 表达式
表达式说明:
-
用空格隔开每一项
-
用反斜杠放在shell特定的字符前面(发现表达式运行错误时,可以试试转义)
-
对包含空格和其他特殊字符的字符串要用引号括起来
-
expr会在
stdout
中输出结果。如果为逻辑关系表达式,则结果为真,stdout
为1,否则为0。 -
expr的
exit code
:如果为逻辑关系表达式,则结果为真,exit code
为0,否则为1。
字符串表达式
-
length STRING
返回
STRING
的长度 -
index STRING CHARSET
CHARSET
中任意单个字符在STRING
中最前面的字符位置,下标从1开始。如果在STRING
中完全不存在CHARSET中的
字符,则返回0。 -
substr STRING POSITION LENGTH
返回STRING
字符串中从POSITION
开始,长度最大为LENGTH
的子串。如果POSITION
或LENGTH
为负数,0或非数值,则返回空字符串。
示例:
str="Hello World!"
echo `expr length "$str"` # ``不是单引号,表示执行该命令,输出12
echo `expr index "$str" aWd` # 输出7,下标从1开始
echo `expr substr "$str" 2 3` # 输出 ell
整数表达式
expr
支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
+ -
加减运算。两端参数会转换为整数,如果转换失败则报错。
* / %
乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。()
可以该表优先级,但需要用反斜杠转义
示例:
a=3
b=4
echo `expr $a + $b` # 输出7
echo `expr $a - $b` # 输出-1
echo `expr $a \\* $b` # 输出12,*需要转义
echo `expr $a / $b` # 输出0,整除
echo `expr $a % $b` # 输出3
echo `expr \\( $a + 1 \\) \\* \\( $b + 1 \\)` # 输出20,值为(a + 1) * (b + 1)
逻辑关系表达式
-
|
如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。如果第一个参数是非空或非0时,不会计算第二个参数。 -
&
如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。 -
< <= = == != >= >
比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。 -
()
可以该表优先级,但需要用反斜杠转义
示例:
a=3
b=4
echo `expr $a \\> $b` # 输出0,>需要转义
echo `expr $a '<' $b` # 输出1,也可以将特殊字符用引号引起来
echo `expr $a '>=' $b` # 输出0
echo `expr $a \\<\\= $b` # 输出1
c=0
d=5
echo `expr $c \\& $d` # 输出0
echo `expr $a \\& $b` # 输出3
echo `expr $c \\| $d` # 输出5
echo `expr $a \\| $b` # 输出3
read命令
read
命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code
为1,否则为0。
参数说明
-
-p
: 后面可以接提示信息 -
-t
:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
实例:
acs@9e0ebfcd82d7:~$ read name # 读入name的值
acwing yxc # 标准输入
acs@9e0ebfcd82d7:~$ echo $name # 输出name的值
acwing yxc #标准输出
acs@9e0ebfcd82d7:~$ read -p "Please input your name: " -t 30 name # 读入name的值,等待时间30秒
Please input your name: acwing yxc # 标准输入
acs@9e0ebfcd82d7:~$ echo $name # 输出name的值
acwing yxc # 标准输出
echo命令
echo
用于输出字符串。命令格式:
echo STRING
显示普通字符串
echo "Hello AC Terminal"
echo Hello AC Terminal # 引号可以省略
显示转义字符
echo "\\"Hello AC Terminal\\"" # 注意只能使用双引号,如果使用单引号,则不转义
echo \\"Hello AC Terminal\\" # 也可以省略双引号
显示变量
name=yxc
echo "My name is $name" # 输出 My name is yxc
显示换行
echo -e "Hi\\n" # -e 开启转义
echo "acwing"
输出结果:
Hi
acwing
显示不换行
echo -e "Hi \\c" # -e 开启转义 \\c 不换行
echo "acwing"
输出结果:
Hi acwing
显示结果定向至文件
echo "Hello World" > output.txt # 将内容以覆盖的方式输出到output.txt中
原样输出字符串,不进行转义或取变量(用单引号)
name=acwing
echo '$name\\"'
输出结果
$name\\"
显示命令的执行结果
echo `date`
输出结果:
Wed Sep 1 11:45:33 CST 2021
printf命令
printf
命令用于格式化输出,类似于C/C++
中的printf
函数。
默认不会在字符串末尾添加换行符。
命令格式:
printf format-string [arguments...]
用法示例
脚本内容:
printf "%10d.\\n" 123 # 占10位,右对齐
printf "%-10.2f.\\n" 123.123321 # 占10位,保留2位小数,左对齐
printf "My name is %s\\n" "yxc" # 格式化输出字符串
printf "%d * %d = %d\\n" 2 3 `expr 2 \\* 3` # 表达式的值作为参数
输出结果:
123.
123.12 .
My name is yxc
2 * 3 = 6
test命令与判断符号[]
逻辑运算符&&和||
-
&&
表示与,||
表示或 -
二者具有短路原则:
expr1 && expr2
:当expr1
为假时,直接忽略expr2
expr1 || expr2
:当expr1
为真时,直接忽略expr2
-
表达式的
exit code
为0,表示真;为非零,表示假。(与C/C++
中的定义相反)
test命令
在命令行中输入man test
,可以查看test
命令的用法。
test
命令用于判断文件类型,以及对变量做比较。
test
命令用exit code
返回结果,而不是使用stdout
。0表示真,非0表示假。
例如:
test 2 -lt 3 # 为真,返回值为0
echo $? # 输出上个命令的返回值,输出0
acs@9e0ebfcd82d7:~$ ls # 列出当前目录下的所有文件
homework output.txt test.sh tmp
acs@9e0ebfcd82d7:~$ test -e test.sh && echo "exist" || echo "Not exist"
exist # test.sh 文件存在
acs@9e0ebfcd82d7:~$ test -e test2.sh && echo "exist" || echo "Not exist"
Not exist # testh2.sh 文件不存在
文件类型判断
命令格式:
test -e filename # 判断文件是否存在
测试参数 | 代表意义 |
---|---|
-e | 文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
文件权限判断
命令格式:
test -r filename # 判断文件是否可读
测试参数 | 代表意义 |
---|---|
-r | 文件是否可读 |
-w | 文件是否可写 |
-x | 文件是否可执行 |
-s | 是否为非空文件 |
整数间的比较
命令格式:
test $a -eq $b # a是否等于b
测试参数 | 代表意义 |
---|---|
-eq | a是否等于b |
-ne | a是否不等于b |
-gt | a是否大于b |
-lt | a是否小于b |
-ge | a是否大于等于b |
-le | a是否小于等于b |
字符串比较
测试参数 | 代表意义 |
---|---|
test -z STRING | 判断STRING是否为空,如果为空,则返回true |
test -n STRING | 判断STRING是否非空,如果非空,则返回true(-n可以省略) |
test str1 == str2 | 判断str1是否等于str2 |
test str1 != str2 | 判断str1是否不等于str2 |
多重条件判定
命令格式:
test -r filename -a -x filename
测试参数 | 代表意义 |
---|---|
-a | 两条件是否同时成立 |
-o | 两条件是否至少一个成立 |
! | 取反。如 test ! -x file,当file不可执行时,返回true |
判断符号[]
[]
与test
用法几乎一模一样,更常用于if
语句中。另外[[]]
是[]
的加强版,支持的特性更多。
例如:
[ 2 -lt 3 ] # 为真,返回值为0
echo $? # 输出上个命令的返回值,输出0
acs@9e0ebfcd82d7:~$ ls # 列出当前目录下的所有文件
homework output.txt test.sh tmp
acs@9e0ebfcd82d7:~$ [ -e test.sh ] && echo "exist" || echo "Not exist"
exist # test.sh 文件存在
acs@9e0ebfcd82d7:~$ [ -e test2.sh ] && echo "exist" || echo "Not exist"
Not exist # testh2.sh 文件不存在
注意:
[]
内的每一项都要用空格隔开- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
例如:
name="acwing yxc"
[ $name == "acwing yxc" ] # 错误,等价于 [ acwing yxc == "acwing yxc" ],参数太多
[ "$name" == "acwing yxc" ] # 正确
判断语句
if…then形式
类似于C/C++
中的if-else
语句。
单层if
命令格式:
if condition
then
语句1
语句2
...
fi
示例:
a=3
b=4
if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ]
then
echo ${a}在范围内
fi
输出结果:
3在范围内
单层if-else
命令格式
if condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
示例:
a=3
b=4
if ! [ "$a" -lt "$b" ]
then
echo ${a}不小于${b}
else
echo ${a}小于${b}
fi
输出结果:
3小于4
多层if-elif-elif-else
命令格式
if condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
else
语句1
语句2
...
fi
示例:
a=4
if [ $a -eq 1 ]
then
echo ${a}等于1
elif [ $a -eq 2 ]
then
echo ${a}等于2
elif [ $a -eq 3 ]
then
echo ${a}等于3
else
echo 其他
fi
输出结果:
其他
case…esac形式
类似于C/C++
中的switch
语句。
命令格式
case $变量名称 in
值1)
语句1
语句2
...
;; # 类似于C/C++中的break
值2)
语句1
语句2
...
;;
*) # 类似于C/C++中的default
语句1
语句2
...
;;
esac
示例:
a=4
case $a in
1)
echo ${a}等于1
;;
2)
echo ${a}等于2
;;
3)
echo ${a}等于3
;;
*)
echo 其他
;;
esac
输出结果:
其他
循环语句
for…in…do…done
命令格式:
for var in val1 val2 val3
do
语句1
语句2
...
done
示例1,输出a 2 cc,每个元素一行:
for i in a 2 cc
do
echo $i
done
示例2,输出当前路径下的所有文件名,每个文件名一行:
for file in `ls`
do
echo $file
done
示例3,输出1-10
for i in $(seq 1 10)
do
echo $i
done
示例4,使用{1…10} 或者 {a…z}
for i in {a..z}
do
echo $i
done
for ((…;…;…)) do…done
命令格式:
for ((expression; condition; expression))
do
语句1
语句2
done
示例,输出1-10,每个数占一行:
for ((i=1; i<=10; i++))
do
echo $i
done
while…do…done循环
命令格式:
while condition
do
语句1
语句2
...
done
示例,文件结束符为Ctrl+d
,输入文件结束符后read
指令返回false。
while read name
do
echo $name
done
until…do…done循环
当条件为真时结束。
命令格式:
until condition
do
语句1
语句2
...
done
示例,当用户输入yes
或者YES
时结束,否则一直等待读入。
until [ "${word}" == "yes" ] || [ "${word}" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done
break命令
跳出当前一层循环,注意与C/C++
不同的是:break
不能跳出case语
句。
示例
while read name
do
for ((i=1;i<=10;i++))
do
case $i in
8)
break
;;
*)
echo $i
;;
esac
done
done
该示例每读入非EOF的字符串,会输出一遍1-7。
该程序可以输入Ctrl+d文件结束符来结束,也可以直接用Ctrl+c杀掉该进程。
continue命令
跳出当前循环。
示例:
for ((i=1;i<=10;i++))
do
if [ `expr $i % 2` -eq 0 ]
then
continue
fi
echo $i
done
该程序输出1-10中的所有奇数。
死循环的处理方式
如果AC Terminal可以打开该程序,则输入Ctrl+c
即可。
否则可以直接关闭进程:
- 使用
top
命令找到进程的PID - 输入
kill -9 PID
即可关掉此进程
函数
bash
中的函数类似于C/C++
中的函数,但return
的返回值与C/C++
不同,返回的是exit code
,取值为0-255,0表示正常结束。
如果想获取函数的输出结果,可以通过echo
输出到stdout
中,然后通过$(function_name)
来获取stdout
中的结果。
函数的return
值可以通过$?
来获取。
命令格式:
[function] func_name() { # function关键字可以省略
语句1
语句2
...
}
不获取 return
值和stdout
值
示例
func() {
name=yxc
echo "Hello $name"
}
func
输出结果:
Hello yxc