Shell是一个命令解释器。与Python,R等脚本语言类似,我们既可以在命令解释器上把命令一行一行敲出来执行,也可以把很多行保存到一个文件(即脚本/script),再让命令解释器执行这个文件。Window里管这个叫批处理(Batch)。因此凡是可以在终端上运行的命令,都可以写到shell脚本里。另外,shell支持变量、分支语句、循环、自定义函数,因此可以用shell脚本实现比较复杂的操作,从而将常规系统管理的操作变成可复用的模块。Shell编程是Linux运维人员必备能力。通过本实验仅能掌握其基本语法,但是想达到企业级应用的水平,需要掌握更多命令,尤其是文本处理命令sed和awk最为重要,以及计划任务、各种系统管理命令等。
编写第一个shell脚本
新建一个shell脚本文件,请注意,文件名后缀一定为.sh。
vi hello.sh
打开vi编辑模式
#!/bin/sh
echo 'Hello World!'
echo命令用于将一个字符串在终端上打印出来。
有两种方法可以用来执行这个脚本。
最简单的方法,用sh命令去解释它。
sh hello.sh
当然,也可以把它作为一个可执行程序运行,但需要添加可执行权限。
chmod +x hello.sh
./hello.sh
Sharp-Band(#!)和注释
Shell脚本的首行要写上“#!/bin/sh”,前面最开始的两个字符“#!”正好组成一个16bit的幻数即0x2321。在unix中执行一个可执行程序时,操作系统将查看该文件的前几个字节,即所谓的“幻数”。它可以用来决定程序的格式以及如何执行它。“#!/bin/sh”表明该script由/bin/sh进行解释执行。——当然,对于大部分Linux系统,你不写同样可以顺利执行,但100%建议你一定要写。
和其他脚本语言一样,Shell脚本只支持单行注释,以“#”开头。
#!/bin/sh
#My first shell script
echo 'Hello World!'
分号的作用
使用分号可以将本应写在两行的内容写在一行上,除此之外并没有使用的必要。
变量
shell中的变量只有一种,就是字符串(当然稍后我们会讲也可以将其作为数值处理)。脚本语言的变量不需要声明,直接赋值即可。
HELLO='Hello World'
注意,“=”的两边绝对不能出现空格。shell脚本是逐句、直接进行解释,如果写了空格,就会被shell认为是一个命令。
单引号和双引号
在shell里,字符串通常并不需要用引号引用才能表示。但如果字符串包含空格的话,就需要引号了,否则就会被认为是多个字符串——语法就会发生错误。
单引号和双引号都可以用来表示字符串,区别在于单引号引用的字符串中的全部字符,包括shell中的特殊字符/语法,都作为普通字符进行处理。而用双引号引用的字符串中的shell符号/语法会进行转义/解释。
变量的引用和打印变量
用$ {变量名}或 $ 变量名引用一个变量。而且可以用${变量名:index:length}轻松地截取字串。
注意$HELLO是我们上面赋值过的那个“Hello World”字符串,而HELLO只是一个内容为“HELLO”的字符串。
echo用来在终端上打印一个字符串。
echo ${HELLO}
echo $HELLO
echo ${HELLO:0:5}
echo ${HELLO:2:5}
echo ${HELLO:2}
请大家通过运行以上命令,自己掌握字符串截取。
set命令可以显示当前工作空间全部的变量和它们的值。
unset 变量名用来清除一个变量。
特殊的变量
| 变量 | 说明 |
|---|---|
| $# | 参数的个数 |
| $number | 第number个参数,例:./my.sh abc,$0的值就是./my.sh,$1的值就是abc |
| $$ | 当前进程的PID |
| $? | 上一次命令的exit值(0-255),0代表没有错误,其他值(1-255)代表有错误 |
| $@ | 代表了参数的内容 |
| $! | Shell最后运行的后台Process的PID |
变量的组合
把字符串放在一起就可以了: $ {variable-name1}${variable-name2}。
例:
TMP_VAR1='Hello, '
TMP_VAR2='World'
echo ${TMP_VAR1}${TMP_VAR2}
变量的默认值/自动初始化
如果变量已经被初始化,则使用它;如果没有被初始化,则使用默认值,但不会对该变量进行初始化。语法:${variable:-value}
例:
echo "This color is ${COLOR:-green}"
#可以打印出green,但是COLOR并没有初始化
echo $COLOR
COLOR=blue
echo "This color is ${COLOR:-green}"
#这次会打印出blue
如果希望使用默认值的同时又进行初始化,则使用:${COLOR:=green}。
要一直使用绝对路径
要一直使用绝对路径,并应该用一个变量保存它。因为你的脚本可能会被移动,或是在其他机器上使用。通过绝对路径及变量,增加你的脚本的可重用性。
例:
SRC_FILE='/tmp/message1'
DST_FILE='/tmp/message2'
cp ${SRC_FILE} ${DST_FILE}
只读变量
MAXVAL=65535
echo ${MAXVAL}
#65535
readonly MAXVAL
MAXVAL=32767
echo ${MAXVAL}
环境变量
学过Java的同学都知道,安装Java之后要设置一个叫PATH的环境变量:要把java和javac的安装目录增添到这个目录里,否则运行java或javac命令时无法找到这两个命令。这个就是环境变量。
环境变量是shell普通变量的一个子集,即环境变量仍是一个“变量”。
有三种方式可以配置环境变量。
方法一:首先已存在一个变量,再用export将其声明为环境变量。(注意:这个环境变量只在当前shell进程中临时创建,登出shell则不复存在。)
VARIABLE-NAME=value
export VARIABLE-NAME
或者一步到位。
export VARIABLE-NAME=value
方法二:修改/etc/profile文件:这个文件在启动shell时会运行,或者在shell运行后使用source /etc/profile重新运行,这个会修改所有用户的环境变量。
方法三:修改自己主目录下的.bash_profile。
环境变量与普通变量最大的区别是:环境变量能传递给子进程。
准备以下脚本testenv.h:
#!/bin/sh
echo $$:$HAHA
运行testenv.sh时,新进程是原来shell的子进程。当HAHA只是普通的变量时,子进程无法访问父进程的普通变量;而当我们把HAHA声明为环境变量后,子进程就可以从父进程那继承环境变量了。
env命令可以查看全部环境变量,比如PATH。
PATH环境变量中用“:“隔开多个路径。当我们在shell上执行一个命令时,就是从PATH中的每个路径中寻找该目录,如果都没有找到,则显示”command not found“。
一般来说,PATH中都不会包含”.“(当前目录),这也是为什么我们运行a.out时需要写”./a.,out“。当然,我们可以把”.“加入到PATH。
此时运行a.out就不必加”.“了。一般情况下,我们不建议把”.“加入PATH。通过下面方法就可以在当前环境下改回去,或重新读取profile文件或重新登录shell亦可。
因为环境变量也是变量,因此也使用unset清除一个环境变量。
I/O
在终端上打印字符串用echo命令。
read
read用于在终端上读取一个字符串,例:
#!/bin/sh
echo 'please input your name age'
read name age
echo "Your name is $name. Your age is $age"
文件重定向
| 命令 | 功能 |
|---|---|
| command > filename | 等价于command 1> filename,将命令的标准输出重定向到文件 |
| command >> filename | 将命令的标准输出追加到到文件 |
| command 2 > filename | 将命令的标准错误输出重定向到文件 |
条件测试(conditional testing)
我们在写分支语句或循环都需要逻辑判断才能确定确定是否执行,逻辑表达式的基本元素就是条件测试。条件测试有两种语法:test condition 或 [ condition ],我们推荐使用后者,更容易嵌入到分支语句和循环中。
Shell中的条件测试本质上是利用进程的返回值,上文已述,0为正常退出,1~255为有错误——因此,在shell的条件测试中,0为真,非0为假——这与C语言正好相反。
测试文件状态
例:tmp.txt可写吗?
[ -w tmp.txt ]
echo $?
也可以写作:
test -w tmp.txt
echo $?
测试字符串
有五种方式用来测试字符串:
[ string1 = string2 ] 判断字符串string1和string2是否相等
[ string1 != string2 ] 判断字符串string1和string2是否不相等
[ -z string ] 判断string字符串是否是空串
[ -n string ] 判断string是否是非空串
[ string ] 判断指定的字符串是否为空
也可以改写为 test 语法。
数值的比较测试
-eq 相等(equal)
-ne 不等(not equal)
-gt 大于(greater than)
-lt 小于(less than)
-ge 大于等于 (greater than or equal)
-le 小于等于 (less than or equal)
例:
NUM=100
[ $NUM -eq 100 ]
echo $?
循环和分支
for each循环
for循环有两种,一种是for each循环,一种是计数型循环。
例:for each循环
#!/bin/sh
for i in 1 3 5 7 9; do
echo $i
done
For each循环如果跟命令替换相结合,功能非常强大。
命令替换的语法有两种:
`命令语句`
$(命令语句)
注意上面第一种那个是反引号,不是引号。
命令替换是指把命令运行的结果替换到你需要进行“命令替换”的位置。
比如
ls -l /
会把该命令的结果直接“复制”“粘贴”到当前位置。
例:For each循环如果跟命令替换相结合
#!/bin/sh
for i in `ls *.c`
do
mv $i sk_$i
done
计数型for循环
例:20次的for循环
#!/bin/sh
for ((i=0;i<20;i=i+1)); do
echo $i
done
while循环
while循环语法比较简单。
while <condition>; do
<stmts>
done
例:输入Ctrl+D(end of file)退出
#!/bin/sh
while read string
do
echo "Your string is $string"
done
echo 'Thank you. Bye!'
直到型循环:until
直到型循环与while的区别是,当condition为真时,while循环一直做;当conditon为假时,until循环才一直做,为真时,循环结束。
until <condition>; do
<stmts>
done
break
break的使用跟其他编程语言无区别。
例:使用break,将上一个shell脚本改写。”:“代表空语句,以便写一个死循环。
#!/bin/sh
while :; do
read string
if [ $? != 0 ]; then
echo 'Thank you. Bye!'
break
fi
echo "Your string is $string"
done
continue
continue的使用跟其他编程语言无区别。
例:
#!/bin/sh
for ((i=0 ;i<20 ;i=$i+1))
do
echo $i
if [ $i -lt 10 ] ; then
continue
fi
echo 'This is a two digital numeric'
done
if then else
shell中的if分支语句只有两点比较特殊:1.代码块的结尾使用”fi“——就是if倒着写;2.else if简写为elif。
例:
#!/bin/sh
# Usage: ./grade.sh grade
# grade should be between 0 and 100, including 0 and 100
if [ $1 -lt '0' ] ; then
echo "Error: invalid grade"
elif [ $1 -lt '60' ] ; then
echo 'no pass'
elif [ $1 -lt '70' ] ; then
echo "pass"
elif [ $1 -lt '80' ] ; then
echo 'good'
elif [ $1 -le '100' ] ; then
echo 'excellent'
else
echo'Error: invalid grade'
fi
case
case语句的语法有些繁复,但通过下面例子就可以掌握。
#!/bin/sh
echo -n "Do you want to continue?"
read yesno
case $yesno in
y | Y | Yes | yes)
echo "We will continue"
;;
n | N | no | NO)
echo "Game over"
;;
*)
echo "Incorrect input"
exit 1
;;
esac
算数计算
数学运算符只是一个一般的字符而已,因此无法计算1+2。
算数计算可以有两种方法。
例:计算4+5的两种方法,后者更适合写到赋值语句中。
expr 4 + 5
$((4+5))
NUM=$((4+5))
自定义函数
语法:
function_name(){
#CMD list
}
例:无参和无返回值的函数
#!/bin/sh
message(){
echo "message"
}
for((i=0;i<3;i++))
do
message
done
例:有参数和返回值的函数
#!/bin/sh
sum() {
x=$1
y=$2
result=$((x+y))
return $result
}
echo "Enter two numbers"
read num1 num2
sum $num1 $num2
echo "The sum is $?"
在shell中,函数本质上就是一个命令。因此,参数即命令的参数:$1, 2,...返回值即2, ... 返回值即2,...返回值即? 也是由于这个原因,返回只能是0~255之间的数,如果超过这个返回,会取其模256后的值。
函数内的变量都是全局变量,如果想声明为函数内的局部变量需使用typeset关键字。
#!/bin/sh
easy(){
typeset a
a=$(($1+$2))
b=$(($1+$2))
echo "easy a is $a"
echo "easy b is $b"
}
a=1
b=2
easy $a $b
echo "global a is $a"
echo "global b is $b"
文件引用
“.”用来引用其他文件。
例:
sum.sh
#!/bin/sh
sum(){
return $(($1+$2))
}
usesum.sh
#!/bin/sh
. ./sum.sh
sum 1 2
echo $?
如有侵权,请立即与博主联系,会尽快删除本文
Shell编程是Linux运维人员必备技能,本文介绍了Shell脚本的基础知识,包括编写第一个脚本、变量、条件测试、循环、函数等。通过实例解析,帮助读者掌握Shell脚本的基本语法和常用操作。
2539

被折叠的 条评论
为什么被折叠?



