linux shell scripts
一. 编写shell脚本的准备
1. shell脚本的内部运行机制
- 命令的运行是从上而下、从左而右的分析与运行;
- 命令的下达: 命令、选项与参数间的多个空白都会被忽略掉;
- 空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
- 如果读取到一个 Enter 符号 (CR) ,就尝试开始运行该行 (或该串) 命令;
- 至於如果一行的内容太多,则可以使用『 \[Enter] 』来延伸至下一行;
- 『 # 』可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略!
2. 如何启动shell脚本
- 直接命令下达: shell.sh 文件必须要具备可读与可运行 (rx) 的权限,然后: (实际上是在bash的子程序里运行)
- 绝对路径:使用 /home/dmtsai/shell.sh 来下达命令;
- 相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来运行
- 变量『PATH』功能:将 shell.sh 放在 PATH 指定的目录内,例如: ~/bin/
- 以 bash 程序来运行:透过『 bash shell.sh 』或『 sh shell.sh 』来运行.(实际上是在bash的子程序里运行)
- 以source来运行脚本(是在父bash程序运行脚本,不推荐)。
3. shell脚本头部内容
- script 的功能;
- script 的版本资讯;
- script 的作者与联络方式;
- script 的版权宣告方式;
- script 的 History (历史纪录);
- script 内较特殊的命令,使用『绝对路径』的方式来下达;
- script 运行时需要的环境变量预先宣告与配置。
4. 编写shell脚本的习惯
- shell脚本头部建议按照第3点进行完善;
- 个人建议务必要加上注解说明,可以帮助你非常非常多;
- 此外,程序码的撰写最好使用巢状方式,在包覆的内部程序码最好能以 [tab] 按键的空格向后推, 这样你的程序码会显的非常的漂亮与有条理!在查阅与 debug 上较为轻松愉快喔;
- 使用撰写 script 的工具最好使用 vim 而不是 vi ,因为 vim 会有额外的语法检验机制,能够在第一阶段撰写时就发现语法方面的问题。
二. 简单脚本功能实践
1. 提示用户输入并拼接用户输入范例
#!/bin/bash # study shell scripts # 2020.03.04 auth:jet PATH=`echo $PATH` export PATH read -p "please input your first name:" first_name read -p "please input your second name:" second_name echo -e "\n your full name is: $first_name$second_name"
2. 根据用户输入创建不同日期文件
#!/bin/bash # study touch different date file # 2020.03.05 auth:jet
PATH=`echo $PATH`
export PATH #提示用户输入文件名 echo -e "I will touch three dinfferent date file." read -p "please input your filename:" fileuser #判断用户是否输入了文件名 filename=${fileuser:-"filename"} #将用户输入的文件名进行不同日期处理 date1=`date --date '2 days ago' +%Y%m%d` date2=`date --date '1 days ago' +%Y%m%d` date3=`date +%Y%m%d` file1=$filename$date1 file2=$filename$date2 file3=$filename$date3 #创建不同日期的文件名 touch $file1 touch $file2 touch $file3
3. 计算器的演示
#!/bin/bash # this is study cal # 2020.03.05 auth:jet PATH=`echo $PATH` export PATH echo -e "this is a * cal" read -p "pls input a:" a read -p "pls input b:" b x=$(($a*$b)) echo -e "a*b=$x"
三. 学习判断工具
1. test判断工具
测试的标志 代表意义 1. 关於某个档名的『文件类型』判断,如 test -e filename 表示存在否 -e 该『档名』是否存在?(常用) -f 该『档名』是否存在且为文件(file)?(常用) -d 该『档名』是否存在且为目录(directory)?(常用) -b 该『档名』是否存在且为一个 block device 装置? -c 该『档名』是否存在且为一个 character device 装置? -S 该『档名』是否存在且为一个 Socket 文件? -p 该『档名』是否存在且为一个 FIFO (pipe) 文件? -L 该『档名』是否存在且为一个连结档? 2. 关於文件的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外) -r 侦测该档名是否存在且具有『可读』的权限? -w 侦测该档名是否存在且具有『可写』的权限? -x 侦测该档名是否存在且具有『可运行』的权限? -u 侦测该档名是否存在且具有『SUID』的属性? -g 侦测该档名是否存在且具有『SGID』的属性? -k 侦测该档名是否存在且具有『Sticky bit』的属性? -s 侦测该档名是否存在且为『非空白文件』? 3. 两个文件之间的比较,如: test file1 -nt file2 -nt (newer than)判断 file1 是否比 file2 新 -ot (older than)判断 file1 是否比 file2 旧 -ef 判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是否均指向同一个 inode 哩! 4. 关於两个整数之间的判定,例如 test n1 -eq n2 -eq 两数值相等 (equal) -ne 两数值不等 (not equal) -gt n1 大於 n2 (greater than) -lt n1 小於 n2 (less than) -ge n1 大於等於 n2 (greater than or equal) -le n1 小於等於 n2 (less than or equal) 5. 判定字串的数据 test -z string 判定字串是否为 0 ?若 string 为空字串,则为 true test -n string 判定字串是否非为 0 ?若 string 为空字串,则为 false。 注: -n 亦可省略 test str1 = str2 判定 str1 是否等於 str2 ,若相等,则回传 true test str1 != str2 判定 str1 是否不等於 str2 ,若相等,则回传 false 6. 多重条件判定,例如: test -r filename -a -x filename -a (and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。 -o (or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。 ! 反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true
#!/bin/bash # this is study test # 2020.03.05 auth:jet PATH=`echo $PATH` export=PATH # 1. 让使用者输入档名,并且判断使用者是否真的有输入字串? echo -e "Please input a filename, I will check the filename's type and \ permission. \n\n" read -p "Input a filename : " filename test -z $filename && echo "You MUST input a filename." && exit 0 # 2. 判断文件是否存在?若不存在则显示信息并结束脚本 test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit 0 # 3. 开始判断文件类型与属性 test -f $filename && filetype="regulare file" test -d $filename && filetype="directory" test -r $filename && perm="readable" test -w $filename && perm="$perm writable" test -x $filename && perm="$perm executable" # 4. 开始输出资讯! echo "The filename: $filename is a $filetype" echo "And the permissions are : $perm"
2. 利用判断符号 [ ]
中括号的使用方法与 test的用法一致,区别在于表现形式,中括号的使用需要注意如下几点:
- 在中括号 [] 内的每个组件都需要有空白键来分隔;
- 在中括号内的变量,最好都以双引号括号起来;
- 在中括号内的常数,最好都以单或双引号括号起来。
举个例子,判断两个变量是否相等。
举例1:
[ "$HOME" == "$MAIL" ] [□"$HOME"□==□"$MAIL"□] ↑ ↑ ↑ ↑
举例2:
read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what your choice is" && exit 0
3. shell scripts的默认变量
脚本的默认变量的位置
/path/to/scriptname opt1 opt2 opt3 opt4 $0 $1 $2 $3 $4
#!/bin/bash PATH=`echo $PATH` export PATH echo "The script name is ==> $0" echo "Total parameter number is ==> $#" # "$#"代表后接的参数『个数』 [ "$#" -lt 2 ] && echo "The number of parameter is less than 2. Stop here." && exit 0 echo "Your whole parameter is ==> '$@'" # "$@"代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来);
echo "Your whole parameter is ==> '$*'" # "$*"代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字节,默认为空白键
echo "The 1st parameter ==> $1" echo "The 2nd parameter ==> $2"
4. shell scripts 变量偏移
#代码: echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'" shift # 进行第一次『一个变量的 shift 』 echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'" shift 3 # 进行第二次『三个变量的 shift 』 echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'" #运行结果: [root@www scripts]# sh sh08.sh one two three four five six <==给予六个参数 Total parameter number is ==> 6 <==最原始的参数变量情况 Your whole parameter is ==> 'one two three four five six' Total parameter number is ==> 5 <==第一次偏移,看底下发现第一个 one 不见了 Your whole parameter is ==> 'two three four five six' Total parameter number is ==> 2 <==第二次偏移掉三个,two three four 不见了 Your whole parameter is ==> 'five six'
四. 条件判断语句
1. if判断语句
基础if语句语法: if [ 条件判断式 ]; then 当条件判断式成立时,可以进行的命令工作内容; fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意! #注意,在中括号判断中&& 代表 AND ;|| 代表 or ;但是亲自测试结果是&& ||不好用,会报语法错误,所以还是老老实实的用-a -o 把。
# 一个条件判断,分成功进行与失败进行 (else) if [ 条件判断式 ]; then 当条件判断式成立时,可以进行的命令工作内容; else 当条件判断式不成立时,可以进行的命令工作内容; fi
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况运行 if [ 条件判断式一 ]; then 当条件判断式一成立时,可以进行的命令工作内容; elif [ 条件判断式二 ]; then 当条件判断式二成立时,可以进行的命令工作内容; else 当条件判断式一与二均不成立时,可以进行的命令工作内容; fi
#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH if [ "$1" == "hello" ]; then echo "Hello, how are you ?" elif [ "$1" == "" ]; then echo "You MUST input parameters, ex> {$0 someword}" else echo "The only parameter is 'hello', ex> {$0 hello}" fi
2. case判断语句
case $变量名称 in <==关键字为 case ,还有变量前有钱字号 "第一个变量内容") <==每个变量内容建议用双引号括起来,关键字则为小括号 ) 程序段 ;; <==每个类别结尾使用两个连续的分号来处理! "第二个变量内容") 程序段 ;; *) <==最后一个变量内容都会用 * 来代表所有其他值 不包含第一个变量内容与第二个变量内容的其他程序运行段 exit 1 ;; esac <==最终的 case 结尾!『反过来写』思考一下!
#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH case $1 in "hello") echo "Hello, how are you ?" ;; "") echo "You MUST input parameters, ex> {$0 someword}" ;; *) # 其实就相当於万用字节,0~无穷多个任意字节之意! echo "Usage $0 {hello}" ;; esac
#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo "This program will print your selection !" # read -p "Input your choice: " choice # 暂时取消,可以替换! # case $choice in # 暂时取消,可以替换! 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
3. shell scripts的函数
function fname() { 程序段 }
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH function printit(){ echo -n "Your choice is " # 加上 -n 可以不断行继续在同一行显示 } echo "This program will print your selection !" case $1 in "one") printit; echo $1 | tr 'a-z' 'A-Z' # 将参数做大小写转换! ;; "two") printit; echo $1 | tr 'a-z' 'A-Z' ;; "three") printit; echo $1 | tr 'a-z' 'A-Z' ;; *) echo "Usage $0 {one|two|three}" ;; esac
#!/bin/bash # function 也是拥有内建变量的~他的内建变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接的变量也是以 $1, $2... 来取代的~ 这里很容易搞错喔~因为『 function fname() { 程序段 } 』内的 $0, $1... 等等与 shell script 的 $0 是不同的。 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH function printit(){ echo "Your choice is $1" # 这个 $1 必须要参考底下命令的下达 } echo "This program will print your selection !" case $1 in "one") printit 1 # 请注意, printit 命令后面还有接参数! ;; "two") printit 2 ;; "three") printit 3 ;; *) echo "Usage $0 {one|two|three}" ;; esac #在上面的例子当中,如果你输入『 sh sh12-3.sh one 』就会出现『 Your choice is 1 』的字样~ 为什么是 1 呢?因为在程序段落当中,我们是写了『 printit 1 』那个 1 就会成为 function 当中的 $1 .
五. 循环语句
1. while循环
while [ condition ] <==中括号内的状态就是判断式 do <==do 是回圈的开始! 程序段落 done <==done 是回圈的结束
until [ condition ] do 程序段落 done
while [ "$yn" != "yes" -a "$yn" != "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
until [ "$yn" == "yes" -o "$yn" == "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
s=0 # 这是加总的数值变量 i=0 # 这是累计的数值,亦即是 1, 2, 3.... while [ "$i" != "100" ] do i=$(($i+1)) # 每次 i 都会添加 1 s=$(($s+$i)) # 每次都会加总一次! done echo "The result of '1+2+3+...+100' is ==> $s"
2. for循环
for var in con1 con2 con3 ... do 程序段 done
#范例1 for animal in dog cat elephant do echo "There are ${animal}s.... " done #范例2 users=$(cut -d ':' -f1 /etc/passwd) # 撷取帐号名称 for username in $users # 开始回圈进行! do id $username finger $username done #范例3 network="192.168.1" # 先定义一个网域的前面部分! for sitenu in $(seq 1 100) # seq 为 sequence(连续) 的缩写之意 do # 底下的程序在取得 ping 的回传值是正确的还是失败的! ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1 # 开始显示结果是正确的启动 (UP) 还是错误的没有连通 (DOWN) if [ "$result" == 0 ]; then echo "Server ${network}.${sitenu} is UP." else echo "Server ${network}.${sitenu} is DOWN." fi done #范例4 # 1. 先看看这个目录是否存在啊? read -p "Please input a directory: " dir if [ "$dir" == "" -o ! -d "$dir" ]; then echo "The $dir is NOT exist in your system." exit 1 fi # 2. 开始测试文件罗~ filelist=$(ls $dir) # 列出所有在该目录下的文件名称 for filename in $filelist do perm="" test -r "$dir/$filename" && perm="$perm readable" test -w "$dir/$filename" && perm="$perm writable" test -x "$dir/$filename" && perm="$perm executable" echo "The file $dir/$filename's permission is $perm " done
for (( 初始值; 限制值; 运行步阶 )) do 程序段 done
read -p "Please input a number, I will count for 1+2+...+your_input: " nu s=0 for (( i=1; i<=$nu; i=i+1 )) do s=$(($s+$i)) done echo "The result of '1+2+3+...+$nu' is ==> $s"
六. shell脚本debug
scripts 在运行之前,最怕的就是出现语法错误的问题了,可以在脚本运行之前通过以下方法进行debug。
[root@www ~]# sh [-nvx] scripts.sh 选项与参数: -n :不要运行 script,仅查询语法的问题;,没有问题不会有任何显示。 -v :再运行 sccript 前,先将 scripts 的内容输出到萤幕上; -x :将使用到的 script 内容显示到萤幕上,这是很有用的参数!