Shell程序的运行原理 Linux Shell脚本类似于Windows的批处理,但它有着比Windows批处理强大很多的功能。Shell脚本实际上是一个Shell命令的堆叠,再配合上Shell的运算,条件判断,循环结构及逻辑判断等语法,使得Shell脚本程序实现强大的功能。那么Shell脚本是怎么运行的呢? 说到Shell脚本的运行我们就先要说说Shell是什么。我们从字面意思可以看出,Shell是一个壳,它即是操作系统提供给用户的管理操作系统的接口,类似于Windows中的Explorer(图形界面)或者cmd命令提示符工具(命令界面DOS),Shell使用的是一种命令语言,它是一种命令解释器,把用户输入的命令解释后然后送由内核进行处理。Linux中有很多不同的Shell程序,最常用的即bash(Bourne Again shell),它是Bourne shell的扩展,而bash脚本中首行的#! /bin/bash即是为了指明该脚本所使用的解释器,Bash读取脚本中程序逻辑并由Bash解释执行,如果是非Bash内置命令,则从PATH中寻找对应的程序调用执行程序指令。
Bash脚本书写规则Bash脚本和一行要给出Shebang即#!/bin/bash ,脚本语句每行一句,如果多句写在一行,则需用;隔开,注释需以#开头
bash环境下的变量
变量即可变更的量,它是一块指向内存的地址空间,用来存储用户输入的数据或者程序运行过程中产生的数值或者对象
本地变量
在Bash中输入SET命令可以看到所有本地定义的Shell变量,作用域为当前Shell进程PATH:指定命令的搜索路径
HOME:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录) HISTSIZE:指保存历史命令记录的条数 LOGNAME:指当前用户的登录名 HOSTNAME:指主机的名称,程序通常从这个环境变量中来取得主机名称 SHELL:指当前用户用的是哪种Shell LANG/LANGUGE:和语言相关的环境变量,使用多种语言的用户可以修改此环境变量 MAIL:指当前用户的邮件存放目录 PS1:命令基本提示符,对于root用户是#,对于普通用户是$ PS2:附属提示符,默认是“>”位置参数变量
在脚本中用于引用传递给脚本的参数;在函数中引用传递给函数的参数,$1-$9表示第1到第9个位置参数,如果第10个则使用${10}局部变量
由用户在程序中定义,作用于函数的执行过程特殊变量
$? 上一次执行的状态码 $0 脚本本身的名字 $# 脚本参数个数 $* 所有的位置参数列表,相当于"$1 $2 $3" 一个字符串,见下面示例程序 $@ 所有的位置参数列表,相当于"$1" "$2" "$3"三个字符串,见下面示例程序 $$ 脚本运行的进程ID 示例程序:#!/bin/bashecho $#echo $0echo $*echo $@echo $$for key in $* #try to take out each elementdo echo $keydonefor key2 in "$*"do echo $key2donefor key3 in $@do echo $key3donefor key4 in "$@"do echo $key4done执行结果:
[root@localhost ~]# bash values.sh 3 2 13values.sh3 2 13 2 13700----values in $* without Quotation Marks-----321----values in $* with Quotation Marks-----3 2 1----values in $@ without Quotation Marks-----321----values in $@ with Quotation Marks-----321
变量的赋值 name=value
name :变量名,只能包含数字、字母和下划线;且不能以数字开头; =:赋值符号 value:值 引用变量:$name ,${name}, "$name"在程序中将替换为变量值,当引用变量后紧跟其它字符时,需使用后两种方式以防止变量名称歧义。使用''时不会对变量进行替换操作而直接显示’’中的原有字符。 如果要引用命令的执行结果,可使用${command}或者使用反单引号`command` declare命令 declare命令用于显示和声明shell变量,当不提供变量名参数时显示所有shell变量,其用法如下: +/-:"-"可用来指定变量的属性,"+"则是取消变量所设的属性 -f:仅显示函数 -r:将变量设置为只读,相当于命令readonly variable_name -x:指定的变量会成为环境变量 -i:声明为整型数值,或者使用let name=value设置变量为整型,然后使用let进行算数运算 后跟name=value即可定义变量并赋值 定义环境变量 export name=[value] 导出变量为环境变量并赋值 declare –x name=[value] 同export效果 变量的撤销 变量从创建开始生效到Shell进程结束终止,如果需要撤消变量,则需要使用unset命令撤消变量 unset variable_name unset命令无法删除readonly属性的变量,readonly变量只有当Shell进程结束后才会撤消 脚本执行方式 ./PATH/TO/SCRIPT_FILE bash /PATH/TO/SCRIPT_FILE bash –x /PATH/TO/SCRIPT_FILE 调试执行脚本,显示详细执行过程 bash –n /PATH/TO/SCRIPT_FILE 检查脚本语法错误 命令状态结果 $? : 上一条命令的执行状态结果 0: 成功 1-255:失败 布尔型: true : 成功 false :失败 自定义状态结果: exit [n] 表示结束脚本程序并返回状态结果n 程序示例:#!/bin/bashecho helloecho $?dirr &> /dev/null #a wrong commandecho $?mkdir -qw test &> /dev/null #command with wrong optionsecho $?exit 5echo $? #因程序退出此命令不会被执行结果:
[root@localhost ~]# bash return.sh hello0127 #return value of unknow command1 #return value of command with wrong option[root@localhost ~]# echo $? #显示脚本运行返回的结果5
条件测试
Linux中的条件测试主要用于判断某种条件是否存在 (1) 根据运行的命令的状态结果 (2) 测试表达式 test EXPRESSION [ EXPRESSION ] ` EXPRESSION ` 条件EXPRESSION是一个表达式,该表达式可为数字、字符串、文本和文件属性的比较,同时可同时加入各种算术、字符串、文本等运算符;'['属于bash内置命令,相当于test ,所以'['和测试语句中间必须要有空格,与其成对出现的 ']' 前也必须要有空格,[会把其后的比较表达式或文件测试当作参数来执行,并返回对应的比较结果(0表示true,1表示false);'[['不属于bash内置命令,和if、for等一样属于bash关键字,双方括号是单方括号的扩展;因逻辑运算和字符测试在单括号中会出现错误的结果,建议使用双括号;
[root@gxd ~]# type [[ is a shell builtin[root@gxd ~]# type forfor is a shell keyword[root@gxd ~]# type [[[[ is a shell keyword
[root@gxd ~]# [ abc < def ][root@gxd ~]# echo $?0 [root@gxd ~]# [ abc > def ][root@gxd ~]# echo $?0 #返回值错误,依旧是0[root@gxd ~]# [[ abc < def ]][root@gxd ~]# echo $?0[root@gxd ~]# [[ abc > def ]][root@gxd ~]# echo $?1 #返回值正确
整数测试:隐含着做数值大小比较,所以不要给变量引用加引用; $A -gt $B:大于 $A -ge $B: 大于等于 $A -lt $B:小于 $A -le $B: 小于等于 $A -eq $B: 等于 $A -ne $B:不等于
字符串测试:按照字符的ASCII码大小进行比较
"$A" > "$B":大于 "$A" < "$B":小于 "$A" == "$B":等于 "$A" != "$B":不等于 -z "$A":是否为空;空则为“真”,否则为“假” -n "$A":是否不空;不空则“真”,空则为“假” =~ : 模式匹配,右边字符是否匹配左边的正则模式注意:须使用` EXPRESSION `
文件测试:测试文件的存在性以及属性;
-e $file: 是否存在;存在则为“真”,否则为“假” -a $file: 同上 -f $file:文件是否存在且为普通文件 -d $file:文件是否存在且为目录 -h $file:是否存在且为符号链接文件 -L $file: 同上 -b $file:是否存在且为块设备文件 -c $file:是否存在且为字符设备文件 -S $file:是否存在且为套接字文件 -p $file: 是否存在且为管道文件-r $file: 当前用户对文件是否拥有读权限
-w $file:当前用户对文件是否拥有写权限 -x $file:当前用户对文件是否拥有执行权限-u $file:文件是否拥有SUID权限
-g $file:文件是否拥有SGID权限 -k $file:文件是否拥有sticky权限-O $file: 当前用户是否为指定文件的属主
-G $file: 当前用户是否为指定文件的属组双目操作符:
$file1 -nt $file2: file1是否新于file2, file1的最近一次的修改时间戳是否晚于file2的 $file1 -ot $file2: file1是否旧于file2, file1的最近一次的修改时间戳是否早于file2的 $file1 -ef $file2:file1与file2是否指向了同一个inode;测试二者是否为同一个文件的硬链接
特殊设备 /dev/null
空设备,bit buckets,可想象为黑洞设备,它会吞下所有数据,并直接丢弃,常用于输出重定向隐藏命令结果输出。 /dev/zero 和/dev/null一样属于伪设备文件,代表二进制0,一般用于对文件或者设备进行填0操作,同样可作为输出重定向以隐藏结果输出。[root@localhost ~]# echo dong &> /dev/null[root@localhost ~]# dirr-bash: dirr: command not found[root@localhost ~]# dirr &> /dev/null[root@localhost ~]# dirr &> /dev/zero
bash之IF条件判断
if/then, caseif CONDITION; then
if-true-分支 fiif CONDITION; then
if-true-分支 else if-false-分支 fi! CONDITION: 取反
shift [n]
移动操作,将位置参数向左移n位,不加n时n的默认值为1,移位后原来的n+1…$#就变为$1…$#-n+1 , n大于等于1小于等于$# , n=0或n>$#时无任何意义 read命令 read [options] VAR... -p "PROMPT" 给出PROMPT提示后等待用户输入 -t timeout 等待用户输入超时时间程序示例:
#!/bin/bash#read -p "Please input a directory path:" filenameif [ -z $filename ]; then echo "input error!" exit 5fiif [ -e $filename ]; then echo "$filename exists." file $filenameelse echo "directory does not exist,start mkdir ${filename}..." mkdir -p $filename if [ $? = 0 ] ; then echo "mkdir successfully!" else echo "mkdir failed!" fifi
循环语句
for, while, until循环:将循环体代码执行0、1或多次;
进入条件:进入循环的条件; 退出条件:循环终止的条件;for VARIABLE in LIST; do
循环体 doneLIST:是由一个或多个空格或换行符分隔开的字符串组成;
把列表的每个字符串逐个赋值给VARIABLE表示的变量;for username in user1 user2 user3; do
循环体 done进入条件:列表非空;
退出条件:列表遍历结束;LIST的生成方法
(1) 整数列表 (a) {start..end} (b) $(seq [start `step` end) (2) 直接给出列表 (3) glob,通配符 (4) 命令生成示例:数值列表
#!/bin/bash#for i in {1..10}; do #seq生成: for i in $(seq 0 1 10); do if id user$i &> /dev/null; then echo "user$i exists." else useradd user$i echo "Add user user$i finished." fidone示例:glob
#!/bin/bash#for filename in /var/log/*; do file $filenamedone
示例:命令生成列表
#!/bin/bash#for username in `cut -d: -f1 /etc/passwd`; do echo "$username primary group: $(id -n -g $username)."done
算术运算
+, -, *, /, %, **次方(1) $[$A+$B]
(2) $(($A+$B)) (3) let VARIABLE=$A+$B (4) VARIABLE=$(expr $A + $B)示例:求100以内所有正整数之和;
#!/bin/bash#declare -i sum=0for (( i=1; i<=100;i++ )); do sum+=$idoneecho "sum of the number from 1 to 100 is:$sum."
练习:求100以内所有偶数之和;
使用至少三种方法实现; 方法一:#!/bin/bash#declare -i sum=0for (( i=0;i<=100;i+=2 )); do sum=$(($sum+$i))doneecho "Even sum: $sum."方法二:
#!/bin/bash#declare -i sum=0for i in $(seq 0 2 100); do sum=$(($sum+$i))doneecho "Even sum: $sum."方法三:
#!/bin/bash#declare -i sum=0for i in {1..100}; doif [ $[$i%2] -eq 0 ]; then sum=$[$sum+$i]fidoneecho "Even sum: $sum."
增强型赋值 += ,-= , *= , /= , %=
sum=$[$sum+$i] 等介于 let sum+=$i let count=$[$count+1] --> let count+=1 --> let count++ let count=$[$count-1] --> let count-=1 --> let count--示例:显示/etc目录下所有普通文件列表,而后统计一共有多少个文件;
#!/bin/bash#declare -i count=0for file in /etc/*; do if [ -f $file ]; then let count++ echo "$count $file" fi done echo "Total: $count files."逻辑运算 条件间逻辑运算: && 与:多个条件要同时满足; || 或:多个条件满足其一即可; ! 非:对指定的条件取反;
表达式组合:
与:` CONDITION1 -a CONDITION2 ` 或:` CONDITION1 -o CONDITION2 ` 非:[ ! CONDITION ]命令组合:
与:COMMAND1 && COMMAND2 <-- [ EXPR1 ] && [ EXPR2 ] 或:COMMAND1 || COMMAND2 非:! COMMAND短路操作符:&&
false && true = false false && false = falsetrue && false = true
true && true = trueif COMMAND1; then
COMMAND2 fi短路操作符:||
true || true = true true || false = truefalse || true = true
false || false = falseif ! COMMAND1; then
COMMAND2 fiCOMMAND1 && COMMAND2 || COMMAND3
if COMMAND1; then COMMAND2 else COMMAND3 fi示例:写一个脚本实现如下功能; 获取当前主机的主机名; 如果当前主机的主机名为空,或者为localhost,则将其修改为
#!/bin/bash#hostname=$(hostname)if [ -z "$hostname" -o "$hostname" == "localhost" ]; then hostname magefi
练习:写一个脚本
(1) 传递两个文本文件路径给脚本; (2) 显示两个文件中空白行数较多的文件及其空白行的个数; (3) 显示两个文件中总行数较多的文件及其总行数;#!/bin/bash#declare -i space1 space2 line1 line2let space1=`grep "^$" $1 | wc -l`let space2=`grep "^$" $2 | wc -l`let line1=`cat $1 | wc -l`let line2=`cat $2 | wc -l`if [ $space1 -gt $space2 ] ; then echo "$1 has $space1 space lines, more than $2."else echo "$2 has $space2 space lines, more than $1."fiif [ $line1 -gt $line2 ] ; then echo "$1 has $line1 lines, more than $2."else echo "$2 has $line2 lines, more than $1."fi
练习:写一个脚本
(1) 提示用户输入一个字符串; (2) 判断: 如果输入的是quit,则退出脚本; 否则,则显示其输入的字符串内容;#!/bin/bash#read -p "Please input a string:" stringif [[ -z $string ]]; then echo "Input error!"elif [[ "$string" == "quit" ]]; then exit 0else echo "the string you input is:$string"fi
练习:写一个脚本,打印九九乘法表;
#!/bin/bash#for i in {1..9};do for j in $(seq 1 1 "$i");do echo -n -e "$j*$i=$[$i*$j]\t" done echodone
多分支的if语句
单分支: if CONDITION; then if-true-分支 fi双分支:
if CONDITION; then if-true-分支 else if-false-分支 fi多分支:
if CONDITION1; then if-CONDITION1-true-分支 elif CONDTION2; then if-CONDITIO2-true-分支 ... else if-ALL-false-分支 fi示例:通过脚本参数传递一个文件路径给脚本,判断其类型;
#!/bin/bash # if [ $# -lt 1 ]; then echo "Usage: $0" exit 1 fiif [ -f $1 ]; then echo "Rgular file." elif [ -d $1 ]; then echo "Directory." elif [ -h $1 ]; then echo "Symbolic link." elif [ -b $1 ]; then echo "Block special." elif [ -c $1 ]; then echo "Charactoer special." elif [ -S $1 ]; then echo "Socket file." else echo "file not exist or unknown type." fi
case语句 简洁版多分支if语句
使用场景:判断某变量的值是否为多种情形中的一种时使用语法:
case $VARIABLE in PATTERN1) 分支1 ;; PATTERN2) 分支2 ;; PATTERN3) 分支3 ;; ... *) 分支n ;; esacPATTERN可使用glob模式的通配符:
*: 任意长度的任意字符; ?: 任意单个字符; []: 指定范围内的任意单个字符; a|b: 多选1;结束符terminator
在每条分支语句后都有一个结束符以决定语句执行完成后的执行流程,';;' 表示执行完语句后跳出case语句;';;&' 表示执行完语句后继续下一个Pattern匹配,可做复杂的case判断;';&' 表示执行完语句后直接执行下个语句,忽略PATTERN匹配。';;' 和';;&'有点类似循环中的break和continue,区别是前者是在case语句中,后者是在循环中。
示例:提示键入任意一个字符;判断其类型;
#!/bin/bashtest_char (){case "$1" in[[:print:]] ) echo "$1 is a printable character.";;&# The ;;& terminator continues to the next pattern test[[:alnum:]] ) echo "$1 is an alpha/numeric character.";;&[[:alpha:]] ) echo "$1 is an alphabetic character.";;# The ;; terminator exit the case body[[:lower:]] ) echo "$1 is a lowercase alphabetic character.";;[[:digit:]] ) echo "$1 is an numeric character.";&# The ;& terminator executes the next statement wether pattern match or not%@ ) echo "********************************";;esac}read -p "Please input a character:" charwhile [[ "$char" != "quit" ]];do test_char $char read -p "Please input a character:" chardone
结果:
[root@gxd ~]# bash terminator.sh Please input a character:ss is a printable character.s is an alpha/numeric character.s is an alphabetic character.Please input a character:## is a printable character.Please input a character:33 is a printable character.3 is an alpha/numeric character.3 is an numeric character.********************************Please input a character:quit
流程控制:
循环语句:for, while, untilwhile循环:
while CONDTION;
do
循环体 done进入条件:当CONDITION为“真”;
退出条件:当CONDITION为“假”;
示例:打印九九乘法表
#!/bin/bash # declare -i i=1 declare -i j=1while [ $j -le 9 ]; do while [ $i -le $j ]; do echo -e -n "${i}X${j}=$[$i*$j]\t" let i++ done echo let i=1 let j++ done
unitl循环
until CONDITION; do 循环体 循环控制变量的修正表达式 done进入条件:当CONDITION为“假”时
退出条件:当CONDITION为“真”时注:同while、for、case这些关键词一样,until一样需要与CONDITION中间有空格。
示例:求100以内所有正整数之和
#!/bin/bash#declare -i sum=0 i=1until [ $i -gt 100 ];do let sum+=$i let i++doneecho "Sum:$sum."
练习1:分别求100以内所有偶数之和,以及所有奇数之和;
#!/bin/bash#declare -i evenSum=0 oddSum=0 i=2 j=1until [ $i -gt 100 ];do let evenSum+=$i let oddSum+=$j let i+=2 let j+=2doneecho "evenSum is:$evenSum"echo "oddSum is:$oddSum"
练习2:实现九九乘法表;
#!/bin/bash#declare -i i=1 j=1until [ $i -gt 9 ];do until [ $j -gt $i ];do echo -n -e "${j}*${i}=$[$i*$j]\t" let j++ done let i++ let j=1 echodone练习3:分别使用while和until循环实现添加10个用户:user1-user10;
#!/bin/bash#declare -i num=1while [ $num -le 10 ];do useradd user$num let num++done#until [ $num -gt 10 ];do# useradd user$num# let num++#done
循环控制
continue [n]:提前结束本轮循环,而直接进入下一轮,n表示直接进入到第n级循环的下一轮循环 break [n]:提前结束退出循环,n表示跳出的循环层数while循环:
while CONDITION; do ....... if CONDITION2; then break [n] fi donewhile CONDITION; do
...... if CONDITION2; then continue [n] fi ...... done示例:求100以内所有偶数之和;
#!/bin/bash # declare -i sum=0declare -i i=0 while [ $i -le 100 ]; do let i++ if [ $[$i%2] -eq 1 ]; then continue fi let sum+=$idone echo "Sum: $sum."
死循环
while true; do 循环体 if CONDTION; then break fi doneuntil false; do
循环体 if CONDITION; then break fi done示例:每隔3秒钟查看当前系统上是否有名为“gentoo”的用户登录;
如果某次查看gentoo登录了,则显示gentoo已经登录; 如果未登录,就显示仍然未来,并显示这是已经是第多少次查看了;
#!/bin/bash#declare -i count=0while true;do if who | grep "^$1" &>/dev/null;then echo "$1 is logged." break else let count++ echo "$count: $1 has not login." fi sleep 3donewhile循环的特殊用法 遍历文件的每一行: while read VARIABLE; do 循环体 done < /PATH/FROM/SOME_FILE
示例:从用户指定的文件读取内容,当名字为dong时停止读取,显示读取到的名字个数;
#!/bin/bash#filename=$1while [ "$name" != "dong" ]do read name echo $name let count+=1done < $filenameecho;echo "$count names read";echo
同样可以使用管道 指定while read所读取的文件:
#!/bin/bash#filename=$1cat $1 |while [ "$name" != "dong" ]do read name echo $name let count+=1doneecho;echo "$count names read";echo
示例:找出UID为偶数的所有用户,显示其用户名和ID号;
#!/bin/bash#while read line; do userid=$(echo $line | cut -d: -f3) if [ $[$userid%2] -eq 0 ]; then echo $line | cut -d: -f1,3 fidone < /etc/passwd
for循环的特殊用法 for ((expr1;expr2;expr3)); do
循环体 doneexpr1: 定义控制变量,并初始赋值;
expr2: 循环控制条件; 进入条件:控制条件为“真” 退出条件:控制条件为“假” expr3: 修正控制变量示例:求100以内所有正整数之和;
#!/bin/bash # declare -i sum=0for ((i=1;i<=100;i++)); do let sum+=$idone echo "Sum: $sum."
函数
和其它编程语言一样,Shell脚本也有函数功能;函数function是一段独立的代码块执行一些特定作业,就像一个黑匣子完成一种特定任务。当有一部分代码执行着一要相同的工作时,我们通常把它定义为函数,以简化代码的编写,也方便了后期的使用。函数作用:
代码重用; 模块化编程;函数的使用方法:
先定义:编写函数代码 后调用:给出函数名,还可按需传递参数函数的参数传递:
和C及其它编程语言不同,Shell脚本函数的参数传递不需要在函数后()内指定需要传递的参数,同样使用Bash的位置参数的方法来传递参数,即$0 $1 $2….
定义方法:
(1) function f_name { 函数体 }(2) f_name() {
函数体 }调用函数:
f_name [argu1, argu2, ...]自定义函数状态返回值:
return [#] 0: 成功 1-255:失败注意:函数代码执行时,一旦遇到return,函数代码终止运行,函数返回;
示例:判断用户的ID号的奇偶性;
#!/bin/bash # evenid() { if [ $# -lt 1 ]; then return 1 fi if ! id $1 &> /dev/null; then return 2 fi userid=$(id -u $1) if [ $[$userid%2] -eq 0 ]; then echo "$1, Even user ID." else echo "$1, Odd user ID." fi}evenid rootevenidecho $?evenid $1echo $?
模块化编程
功能:把脚本文件中的代码分隔为多段,放在不同的文件中 假设/root/bin/srv目录有两个文件: (1) 函数文件 (2) 脚本文件为脚本使用配置文件
一个文件中只定义变量 脚本文件source此变量定义的文件变量的作用域:
局部变量: local VARIABLE=value存活时间:
函数执行开始,至函数返回结束;END