以前在ubuntu运行软件的时候,因为不懂shell脚本,因此出了什么问题全是靠百度来解决,走前人走过的路,摸着前人的石头过河。这样短期还行,但显然不是长久之计,如今要逐步开始学习Fabric了,它的第一个例子的运行文件e2e_cli的network_setup.sh就难住了我,虽然依赖百度花费近十天终于将之运行了起来但是不能这样下去了。浑浑噩噩这么久,是时候学习一下shell脚本了。
Shell是操作系统的最外层,管理用户与操作系统之间的交互;等待你的输入,向操作系统输入你的操作,并且处理各种操作系统的输出结果。
Shell 除了能解释用户输入的命令,将它传递给操作系统,还可以:
由此可见,Shell 是将操作系统、程序和用户连接了起来。
Shell 也是一种编程语言,它的编译器(解释器)是 Shell 这个程序。我们平时所说的 Shell,有时候是指连接用户和内核的这个程序,有时候又是指 Shell 编程。
Shell 主要用来开发一些实用的、自动化的小工具,而不是用来开发具有复杂业务逻辑的中大型软件,例如检测计算机的硬件参数、一键搭建Web开发环境、日志分析等,Shell 都非常合适。
常见的 Shell 有 sh、bash、csh、tcsh、ash 等。
bash shell 是 Linux 的默认 shell
bash 由 GNU 组织开发,保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。
尽管如此,bash 和 sh 还是有一些不同之处:
一方面,bash 扩展了一些命令和参数;
另一方面,bash 并不完全和 sh 兼容,它们有些行为并不一致,但在大多数企业运维的情况下区别不大,特殊场景可以使用 bash 代替 sh。
查看当前Linux的shell可以用以下命令(SHELL要大写):
echo $SHELL
#我的输出结果是/bin/bash
Ubuntu16.03的shell终端就是Terminology。
Tips:
Shell提示符之中,$代表的是普通用户,#代表的是root用户。
啥也别说了,编程第一步,Hello World!
新建一个脚本文件test.sh,里面输入如下内容:
#!/bin/bash
echo "Hello World !"
#!是一个约定,告诉系统这个脚本用什么解释器来执行。
Echo是在窗口输出后面的内容。
接着在该文件的同级目录里面打开终端,键入如下内容:
chmod +x ./test.sh
./test.sh
第一行是使之能够执行,第二行是执行该脚本。结果如下:
执行.sh脚本有6种方式,感兴趣的可以参考下面这两位的博客,https://blog.csdn.net/qq_37699336/article/details/80724700
https://blog.csdn.net/zhangxl_ly/article/details/79352644
#开头,单行注释,shell只支持单行注释。
由export关键字处理过的变量叫做环境变量。
a=“helloworld”
echo $a
echo “This is ${a} !”
输出结果如下:
一般来说,单引号引起来的内容都是直接输出,即使有变量和命令也不例外,换句话说,在单引号内,变量和命令无效;双引号会先解析里面的变量和命令,然后再输出。
Log=$(cat log.txt)
Echo $log
readonly zyx
只能删除普通变量,无法删除只读变量
unset zyx
$* 和 $@ 的区别为: $* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。
$? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1。
命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。
DATE=`date`
echo "Date is $DATE"
#结果如下
Date is Fri Jan 18 09:15:58 CST 2019
可以根据变量的状态(是否为空、是否定义等)来改变它的值.
使用expr进行操作
val=`expr 2 + 2`
echo "Total value : $val"
注意:
只支持数字,不支持字符串,除非这个字符串里面全是数字,当然别忘了空格
your_name="zyx"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
结果
hello, zyx ! hello, zyx !
string="abcd"
echo ${#string} #输出 4
string="zyx is a boy"
echo ${string:1:4} #输出yx i
string="zyx is a boy who is very good!"
echo `expr index "$string" is` #输出5
只支持一维数组,下标的范围没有限制
在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
array_name=(value0 value1 value2 value3)
读取的格式一般是
${array_name[index]}
使用@ 或 * 可以获取数组中的所有元素,例如:
${array_name[*]}
${array_name[@]}
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
prinft没有自带的换行,需要手动增加”\n”。
和C相比,bash的printf不需要加括号。
printf "Hello, Shell\n"
printf "%d %s\n" 1 "abc"
#输出1 abc
直接上if..elif..fi吧
if [ expression 1 ]
then
Statement(s) to be executed if expression 1 is true
elif [ expression 2 ]
then
Statement(s) to be executed if expression 2 is true
elif [ expression 3 ]
then
Statement(s) to be executed if expression 3 is true
else
Statement(s) to be executed if no expression is true
fi
case…esac语句
同switch语句,break用;;代替,default用*代替。
取值后面必须为关键字 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至;;
case 值 in
模式1)
command1
command2
command3
;;
模式2)
command1
command2
command3
;;
*)
command1
command2
command3
;;
esac
for 变量 in 列表
do
command1
command2
...
commandN
done
列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。
in 列表是可选的,如果不用它,for 循环使用命令行的位置参数。
while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:
while command
do
Statement(s) to be executed if command is true
Done
until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。
until command
do
Statement(s) to be executed until command is true
done
当然,shell也有break和continue,作用于循环之中。另外,break 命令后面还可以跟一个整数,表示跳出第几层循环。
先定义后使用,我推荐在函数前加上关键字 function
function MyFunction () {
list of commands
[ return value ]
}
调用函数只需要给出函数名,不需要加括号。
函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”。如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。
删除变量一样,删除函数也可以使用 unset 命令,不过要加上 .f 选项
unset .f MyFunction
调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...
当n>=10时,需要加上{},因此我推荐统一使用${n}来获取参数的值。
Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。命令的输出不仅可以是显示器,还可以很容易的转移向到文件,这被称为输出重定向。
重定向>会覆盖文件内容,可以使用 >> 追加到文件末尾
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
Here Document 目前没有统一的翻译,这里暂译为”嵌入文档“。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本的形式如下:
command << delimiter
document
delimiter
它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
注意:
结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
开始的delimiter前后的空格会被忽略掉。
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。
Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本。
Shell 中包含脚本可以使用:
. filename
或
source filename
两种方式的效果相同,简单起见,一般使用点号(.),但是注意点号(.)和文件名中间有一空格。被包含脚本不需要有执行权限。
例子如图所示,使用main.sh调用test.sh的变量。
https://www.cnblogs.com/yinheyi/p/6648242.html
http://c.biancheng.net/cpp/view/6994.html
http://swiftlet.net/archives/category/swiftlet-shell/funny-shell(趣谈shell)