您的位置:首页 > 运维架构 > Shell

Shell

2015-09-24 18:46 591 查看
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进程
环境变量
定义当前Shell运行的环境信息,如PATH、PWD、HOME等,作用域为当前Shell进程及其子进程;环境变量可以用env、printenv、export查看,或者使用echo $变量名,下面是一些常用 的环境变量
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/bash

echo $#
echo $0
echo $*
echo $@
echo $$

for key in $*      #try to take out each element
do
echo $key
done

for key2 in "$*"
do
echo $key2
done

for key3 in $@
do
echo $key3
done

for key4 in "$@"
do
echo $key4
done

执行结果:

[root@localhost ~]# bash values.sh 3 2 1
3
values.sh
3 2 1
3 2 1
3700
----values in $* without Quotation Marks-----
3
2
1
----values in $* with Quotation Marks-----
3 2 1
----values in $@ without Quotation Marks-----
3
2
1
----values in $@ with Quotation Marks-----
3
2
1


变量的赋值

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

程序示例:

#!/bin/bash

echo hello
echo $?
dirr &> /dev/null            #a wrong command
echo $?
mkdir -qw test &> /dev/null  #command with wrong options
echo $?
exit 5
echo $?                      #因程序退出此命令不会被执行
结果:

[root@localhost ~]# bash return.sh
hello
0
127                          #return value of unknow command
1                            #return value of command with wrong option
[root@localhost ~]# echo $?  #显示脚本运行返回的结果
5


条件测试

Linux中的条件测试主要用于判断某种条件是否存在

(1) 根据运行的命令的状态结果

(2) 测试表达式

test EXPRESSION

[ EXPRESSION ]

[[ EXPRESSION ]]

条件EXPRESSION是一个表达式,该表达式可为数字、字符串、文本和文件属性的比较,同时可同时加入各种算术、字符串、文本等运算符;'['属于bash内置命令,相当于test ,所以[b]'['和测试语句中间必须要有空格,与其成对出现的 ']' 前也必须要有空格,[会把其后的比较表达式或文件测试当作参数来执行,并返回对应的比较结果(0表示true,1表示false);'[['不属于bash内置命令,和if、for等一样属于bash关键字,双方括号是单方括号的扩展;因逻辑运算和字符测试在单括号中会出现错误的结果,建议使用双括号;[/b]

[root@gxd ~]# type [
[ is a shell builtin
[root@gxd ~]# type for
for 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, case

if CONDITION; then

if-true-分支

fi

if CONDITION; then

if-true-分支

else

if-false-分支

fi

! CONDITION: 取反

shift

移动操作,将位置参数向左移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:" filename
if [ -z $filename ]; then
echo "input error!"
exit 5
fi

if [ -e $filename ]; then
echo "$filename exists."
file $filename
else
echo "directory does not exist,start mkdir ${filename}..."
mkdir -p $filename
if [ $? = 0 ] ; then
echo "mkdir successfully!"
else
echo "mkdir failed!"
fi
fi


循环语句
for, while, until

循环:将循环体代码执行0、1或多次;
进入条件:进入循环的条件;

退出条件:循环终止的条件;

for VARIABLE in LIST; do

循环体

done

LIST:是由一个或多个空格或换行符分隔开的字符串组成;

把列表的每个字符串逐个赋值给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."
fi
done


示例:glob

#!/bin/bash
#
for filename in /var/log/*; do
file $filename
done


示例:命令生成列表

#!/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=0

for (( i=1; i<=100;i++ )); do
sum+=$i
done

echo "sum of the number from 1 to 100 is:$sum."


练习:求100以内所有偶数之和;

使用至少三种方法实现;

方法一:

#!/bin/bash
#
declare -i sum=0

for (( i=0;i<=100;i+=2 )); do
sum=$(($sum+$i))
done

echo "Even sum: $sum."


方法二:

#!/bin/bash
#
declare -i sum=0

for i in $(seq 0 2 100); do
sum=$(($sum+$i))
done

echo "Even sum: $sum."


方法三:

#!/bin/bash
#
declare -i sum=0

for i in {1..100}; do
if [ $[$i%2] -eq 0 ]; then
sum=$[$sum+$i]
fi
done

echo "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=0

for 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 = false

true && false = true

true && true = true

if COMMAND1; then

COMMAND2

fi

短路操作符:||

true || true = true

true || false = true

false || true = true

false || false = false

if ! COMMAND1; then

COMMAND2

fi

COMMAND1 && COMMAND2 || COMMAND3

if COMMAND1; then

COMMAND2

else

COMMAND3

fi

示例:写一个脚本实现如下功能;

获取当前主机的主机名;

如果当前主机的主机名为空,或者为localhost,则将其修改为mage

#!/bin/bash
#
hostname=$(hostname)

if [ -z "$hostname" -o "$hostname" == "localhost" ]; then
hostname mage
fi


练习:写一个脚本

(1) 传递两个文本文件路径给脚本;

(2) 显示两个文件中空白行数较多的文件及其空白行的个数;

(3) 显示两个文件中总行数较多的文件及其总行数;

#!/bin/bash
#
declare -i space1 space2 line1 line2
let 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."
fi
if [ $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:" string

if [[ -z $string ]]; then
echo "Input error!"
elif [[ "$string" == "quit" ]]; then
exit 0
else
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
echo
done


多分支的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 <path>"
exit 1
fi

if [ -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

;;

esac

PATTERN可使用glob模式的通配符:

*: 任意长度的任意字符;

?: 任意单个字符;

[]: 指定范围内的任意单个字符;

a|b: 多选1;

结束符terminator

在每条分支语句后都有一个结束符以决定语句执行完成后的执行流程,';;' 表示执行完语句后跳出case语句;';;&' 表示执行完语句后继续下一个Pattern匹配,可做复杂的case判断;';&' 表示执行完语句后直接执行下个语句,忽略PATTERN匹配。';;' 和';;&'有点类似循环中的break和continue,区别是前者是在case语句中,后者是在循环中。

示例:提示键入任意一个字符;判断其类型;

#!/bin/bash
test_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:" char
while [[ "$char" != "quit" ]];
do
test_char $char
read -p "Please input a character:" char
done


结果:

[root@gxd ~]# bash terminator.sh
Please input a character:s
s 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:3
3 is a printable character.
3 is an alpha/numeric character.
3 is an numeric character.
********************************
Please input a character:quit


流程控制:

循环语句:for, while, until

while循环:

while CONDTION;

do

循环体

done

进入条件:当CONDITION为“真”;

退出条件:当CONDITION为“假”;

示例:打印九九乘法表

#!/bin/bash
#
declare -i i=1
declare -i j=1

while [ $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=1

until [ $i -gt 100 ];do
let sum+=$i
let i++
done
echo "Sum:$sum."


练习1:分别求100以内所有偶数之和,以及所有奇数之和;

#!/bin/bash
#
declare -i evenSum=0 oddSum=0 i=2 j=1
until [ $i -gt 100 ];do
let evenSum+=$i
let oddSum+=$j
let i+=2
let j+=2
done
echo "evenSum is:$evenSum"
echo "oddSum is:$oddSum"


练习2:实现九九乘法表;

#!/bin/bash
#
declare -i i=1 j=1
until [ $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
echo
done


练习3:分别使用while和until循环实现添加10个用户:user1-user10;

#!/bin/bash
#
declare -i num=1
while [ $num -le 10 ];do
useradd user$num
let num++
done
#until [ $num -gt 10 ];do
#    useradd user$num
#    let num++
#done


循环控制

continue
:提前结束本轮循环,而直接进入下一轮,n表示直接进入到第n级循环的下一轮循环
break
:提前结束退出循环,n表示跳出的循环层数

while循环:

while CONDITION; do

.......

if CONDITION2; then

break

fi

done

while CONDITION; do

......

if CONDITION2; then

continue

fi

......

done

示例:求100以内所有偶数之和;

#!/bin/bash
#
declare -i sum=0
declare -i i=0

while [ $i -le 100 ]; do
let i++
if [ $[$i%2] -eq 1 ]; then
continue
fi
let sum+=$i
done

echo "Sum: $sum."


死循环

while true; do

循环体

if CONDTION; then

break

fi

done

until false; do

循环体

if CONDITION; then

break

fi

done

示例:每隔3秒钟查看当前系统上是否有名为“gentoo”的用户登录;

如果某次查看gentoo登录了,则显示gentoo已经登录;

如果未登录,就显示仍然未来,并显示这是已经是第多少次查看了;

#!/bin/bash
#
declare -i count=0
while 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 3
done


while循环的特殊用法
遍历文件的每一行:

while read VARIABLE; do

循环体

done < /PATH/FROM/SOME_FILE

示例:从用户指定的文件读取内容,当名字为dong时停止读取,显示读取到的名字个数;

#!/bin/bash
#
filename=$1
while [ "$name" != "dong" ]
do
read name
echo $name
let count+=1
done < $filename
echo;echo "$count names read";echo


同样可以使用管道 指定while read所读取的文件:

#!/bin/bash
#
filename=$1
cat $1 |
while [ "$name" != "dong" ]
do
read name
echo $name
let count+=1
done
echo;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
fi
done < /etc/passwd


for循环的特殊用法
for ((expr1;expr2;expr3)); do

循环体

done

expr1: 定义控制变量,并初始赋值;

expr2: 循环控制条件;

进入条件:控制条件为“真”

退出条件:控制条件为“假”

expr3: 修正控制变量

示例:求100以内所有正整数之和;
#!/bin/bash
#
declare -i sum=0

for ((i=1;i<=100;i++)); do
let sum+=$i
done

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 root

evenid
echo $?

evenid $1
echo $?


模块化编程

功能:把脚本文件中的代码分隔为多段,放在不同的文件中

假设/root/bin/srv目录有两个文件:

(1) 函数文件

(2) 脚本文件

为脚本使用配置文件

一个文件中只定义变量

脚本文件source此变量定义的文件

变量的作用域:

局部变量:

local VARIABLE=value

存活时间:

函数执行开始,至函数返回结束;

END
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: