shell脚本总结

shell脚本简要总结

脚本调试

bash -n /path/to/some_script
检测脚本中的语法错误

bash -x /path/to/some_script
调试执行

变量

环境变量

变量声明、赋值:
export name=VALUE
declare -x name=VALUE

变量引用:$name, ${name}

显示所有环境变量

  • export

  • env

  • printenv

  • 删除:unset name

bash有许多内建的环境变量:PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1

只读和位置变量

只读变量:只能声明,但不能修改和删除
readonly name
declare -r name

位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, …:对应第1、第2等参数,shift [n]换位置
$0: 命令本身
$*: 传递给脚本的所有参数,全部参数合为一个字符串
$@: 传递给脚本的所有参数,每个参数为独立字符串
$#: 传递给脚本的参数的个数
$@ $* 只在被双引号包起来的时候才会有差异

示例:判断给出的文件的行数
linecount="$(wc -l $1| cut -d' ' -f1)"
echo "$1 has $linecount lines."

变量的运算

bash中的算术运算:help let
+, -, *, /, %取模(取余), **(乘方)
增强型赋值:
+=, -=, *=, /=, %=,++,–
实现算术运算:
(1) let var=算术表达式
(2) var=$[算术表达式]
(3) var=$((算术表达式))
(4) var=$(expr arg1 arg2 arg3 …)
(5) declare –i var = 数值
(6) echo "算术表达式" | bc

bash有内建的随机数生成器:$RANDOM(1-32767)

echo $[$RANDOM%50] :0-49之间随机数

注意:
    乘法符号有些场景中需要转义,如  *

逻辑运算  &&  ||   !
短路运算:
短路与:
第一个为0,结果必定为0;
第一个为1,第二个必须要参与运算;
短路或:
第一个为1,结果必定为1;
第一个为0,第二个必须要参与运算;

退出状态

进程使用退出状态来报告成功或失败

•  0  代表成功,1-255代表失败
•  $? 变量保存最近的命令退出状态
例如:
   $ ping -c1 -W1 hostdown &> /dev/null
   $ echo $?

bash自定义退出状态码

exit [n]:自定义退出状态码;

注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

条件测试

判断某需求是否满足,需要由测试机制来实现;专用的测试表达式需要由测试命令辅助完成测试过程;

评估布尔声明,以便用在条件性执行中

  • 若真,则返回0

  • 若假,则返回1

    测试命令:

  • test EXPRESSION

  • [ EXPRESSION ]

  • [[ EXPRESSION ]]
    注意:EXPRESSION前后必须有空白字符

    test命令
    长格式的例子:
    $ test "$A" == "$B" && echo "Strings are equal"
    $ test “$A” -eq “$B” \
    && echo "Integers are equal"
    简写格式的例子:
    $ [ "$A" == "$B" ] && echo "Strings are equal"
    $ [ "$A" -eq "$B" ] && echo "Integers are equal"

bash的测试类型

数值测试:

  • -gt: 是否大于

  • -ge: 是否大于等于

  • -eq: 是否等于

  • -ne: 是否不等于

  • -lt: 是否小于

  • -le: 是否小于等于

    字符串测试:

  • ==:是否等于;

  • >: ascii码是否大于ascii码

  • <: 是否小于

  • !=: 是否不等于

  • =~: 左侧字符串是否能够被右侧的PATTERN所匹配
    注意: 此表达式一般用于[[ ]]中;扩展的正则表达式

  • -z "STRING":字符串是否为空,空为真,不空为假

  • -n "STRING":字符串是否不空,不空为真,空为假
    注意:用于字符串比较时的用到的操作数都应该使用引号

文件测试

存在性测试

  • -a FILE:同-e

  • -e FILE: 文件存在性测试,存在为真,否则为假

存在性及类别测试

  • -b FILE:是否存在且为块设备文件

  • -c FILE:是否存在且为字符设备文件

  • -d FILE:是否存在且为目录文件

  • -f FILE:是否存在且为普通文件

  • -h FILE 或 -L FILE:存在且为符号链接文件

  • -p FILE:是否存在且为命名管道文件

  • -S FILE:是否存在且为套接字文件

文件测试

文件权限测试:

  • -r FILE:是否存在且可读

  • -w FILE: 是否存在且可写

  • -x FILE: 是否存在且可执行

文件特殊权限测试

  • -u FILE:是否存在且拥有suid权限

  • -g FILE:是否存在且拥有sgid权限

  • -k FILE:是否存在且拥有sticky权限

文件测试

文件大小测试:

  • -s FILE: 是否存在且非空

文件是否打开:

  • -t   fd: fd表示文件描述符是否已经打开且与某终端相关

  • -N    FILE:文件自动上一次被读取之后是否被修改过

  • -O   FILE:当前有效用户是否为文件属主

  • -G   FILE:当前有效用户是否为文件属组

双目测试:
FILE1  -ef   FILE2: FILE1与FILE2是否指向同一个设备上的相同inode
FILE1   -nt   FILE2: FILE1是否新于FILE2
FILE1   -ot   FILE2: FILE1是否旧于FILE2

组合测试条件

第一种方式:
COMMAND1 && COMMAND2 并且
COMMAND1 || COMMAND2 或者 ! COMMAND  非

如:[[ -r FILE ]] && [[ -w FILE ]]

第二种方式
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
! EXPRESSION
必须使用测试命令进行;

 示例:
# [ -z “$HOSTNAME” -o $HOSTNAME "==\
"localhost.localdomain" ] && hostname www.magedu.com
# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab

使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量:

  • -p 指定要显示的提示

  • -t 指定读取值时等待的时间(秒)。

    read 从标准输入中读取值,给每个单词分配一个变量所有剩余单词都被分配给最后一个变量

    read -p “Enter a filename: “ FILE

其他

read -n 2 var
从输入中读取两个字符并存入变量var,不需要按回车读取。

read -r line
允许输入包含反斜杠

read -d ":" var
用定界符“:”结束输入行。

实例

从标准输入读取输入并赋值给变量name。

#read name         ------ #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量
HelloWorld     ---------- #控制台输入Hello

#echo $name         -------#打印变量

HelloWorld等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。

#read one two three
1  2  3                      ---------#在控制台输入1 2 3,它们之间用空格隔开。

#echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3

REPLY示例

#read                  #等待控制台输入,并将结果赋值给特定内置变量REPLY。
This is REPLY          #在控制台输入该行。

#echo $REPLY           #打印输出特定内置变量REPLY,以确认是否被正确赋值。

This is REPLY

-p 选项示例

#read -p "Enter your name: "        ---     #输出文本提示,同时等待输入,并将结果赋值给REPLY。
Enter you name: stephen     ------#在提示文本之后输入stephen

#echo $REPLY
stephen

流程控制各个命令的简要语法格式

条件选择if语句

选择执行:

注意:if语句可嵌套单分支
if 判断条件;then
条件为真的分支代码
fi

双分支

if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi

多分支

if 判断条件1; then
if-true
elif 判断条件2; then
if -ture
elif 判断条件3; then
if -ture

else
all-false
fi

根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then 
echo 'Station1 is UP' 
elif grep "station1" ~/maintenance.txt &> /dev/null
then 
echo 'Station1 is undergoing maintenance‘
else 
echo 'Station1 is unexpectedly DOWN!' 
exit 1 
fi

写一个脚本 /root/bin/filetype.sh, 判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)

第一种方法:
if [[ $# -lt 1 ]]
then
    echo -e "Error: No argument.\n\tUsage: $0 FILENAME"
    exit 1
else
    if [[ -e $1 ]]
    then
        FileType=`ls -ld $1 | cut -c1`
        case $FileType in
            d)
                echo "$1 is a diretory"
                ;;
            -)
                echo "$1 is a common file"
                ;;
            l)
                echo "$1 is a link file"
                ;;
            *)
                echo "$1 is other file"
        esac
    else
        echo "$1: no such file or diretory."
    fi
fi
unset FileType

第二种方法

if [[ $# -lt 1 ]]
then
    echo -e "Error: No argument.\n\tUsage: $0 FILENAME"
    exit 1
else
    if [[ ! -e $1 ]]
    then
        echo "$1: No such file or diretory"
    elif [[ -d $1 ]]
    then
        echo "$1 is a diretory"
    elif [[ -L $1 ]]
    then
        echo "$1 is a link file"
    elif [[ -f $1 ]]
    then
        echo "$1 is a common file"
    fi
fi

条件判断:case语句

语法

case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;

*)
默认分支
;;
esac

1、编写脚本/root/bin/createuser.sh,实现如下功能:使
用一个用户名做为参数,如果指定参数的用户存在,就显示
其存在,否则添加之;显示添加的用户的id号等信息

#!/bin/bash
#
read -p "请输入一个ID :" Id
id $Id &/dev/null

if [ $? -eq  0 ];then
        echo "用户$Id已存在"     
else
        useradd $Id
        echo "$Id用户已创建"
        id $Id
fi

for循环

格式 1

for 变量名 in 列表;do
循环体
done

执行机制:

依次将列表中的元素赋值给“变量名”; 每次赋值后即执
行一次循环体; 直到列表中的元素耗尽,循环结束

列表可以由命令命令 or $(命令) 生成或者指定

格式2 (c语言格式)for (( i=1 ; i<=9 ; i++ ))

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done

控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

注意:(())双括号

(( ... )): (( 表达式 ))
估值算术表达式。
表达式按照算术法则进行估值。 等价于 "let 表达式".

退出状态
如果表达式估值为0则返回 1;否则返回0。

例:

# 99乘法表

for i in `seq 9`
do
    for j in `seq 1 $i`
    do
            echo -ne "$i*$j=$(($i*$j))\t"
    done
    echo
done

echo "==================== plan 2 ===================="

for (( i=1 ; i<=9 ; i++ ))
do
    for (( j=1 ; j<=i ; j++ ))
    do
        echo -ne "$i*$j=$(($i*$j))\t"
    done
    echo
done

unset i
unset j

while循环

语法:

while CONDITION; do
循环体
done

  • CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环

  • 因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正

    进入条件:CONDITION为true; 若想死循环  while true
    退出条件:CONDITION为false


1.编写脚本,利用变量RANDOM生成10个随机数字,输出
这个10数字,并显示其中的最大者和最小者

#!/bin/bash
#qzx
#
i=10
a=$RANDOM
max=$a
min=$a
while [ $i -ge 1 ]
do
    [ $max -lt $a ] && max=$a
    [ $min -gt $a ] && min=$a
    echo "$a"
    a=$RANDOM
    let i--
done
echo "最大值$max"
echo "最小值$min"

2.* 金字塔

   *
  ***
 *****
*******

#!/bin/bash
#qzx
#
read -p "请输入要打印的行数: " j
let xing=2*j-1
for i in `seq 1 2 $xing`
do

        let j--
        let a=j
        while [ $a -gt  0 ]
        do
                echo -n " "
                let a--
        done

        for b in `seq 1 $i`
        do
                echo -n "*"
        sleep 0.1
        done
        echo -ne "\n"
done

until循环

语法 1

until CONDITION; do
循环体
done

进入条件: CONDITION 为 false

退出条件: CONDITION 为 true

语法2

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

while read line; do
循环体
done < /PATH/FROM/SOMEFILE

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将
行赋值给变量line

语法1

#!/bin/bash
#打印国际象棋棋盘
#qzx
read -p "NxN:please input N " N
let i=$N
let j=$N

until [ $i -le 0 ]
do
    let j=N
    until [ $j -le 0 ]
    do
        let a=i+j
        let a=a%2
        if [ $a -eq 0 ]
        then
            echo -ne "\033[41m  \033[0m"
        else 
            echo -ne "\033[42m  \033[0m"
        fi
        let j=j-1
    done
    echo -ne "\n"
    let i=i-1
done

语法2

#!/bin/bash
#
#qzx
while read gai
do
    name=$(echo "$gai" | cut -d: -f1)
    numb=$(echo "$gai" | cut -d: -f5)
        if [[ -z "$numb" ]]
        then
            chfn -f $name $name &>/dev/null
            chfn -p "1234567" $name &>/dev/null    
            echo ""$name"添加成功"
        fi

done < ~/bin/passwd

循环控制语句

continue

用于循环体中(do —–continue—- done)

continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层。默认为1

break

用于循环体中(do —-break—-done)

break [N]:提前结束第N层循环,最内层为第1层。默认为1

例、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并提示该用户退出系统。

#/bin/bash
until who |grep -q "^hacker\b" ;do
    sleep 3
done
who | grep "^hacker"|tr -s ' '|cut -d' ' -f3,5 >> /var/log/login.log
echo "you should logout system" | mail hacker
echo "reminded and login record in /var/log/login.log"

select 循环与菜单

语法:

select variable in list
do
循环体命令
done

select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入

  • 用户输入菜单列表中的某个数字,执行相应的命令

  • 用户输入被保存在内置变量 REPLY 中。

  • select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环。

    • select 经常和 case 联合使用

    • 与 for 循环类似,可以省略 in list ,此时使用位置参量

函数

函数由两部分组成:函数名和函数体。

语法一:

function f_name {
…函数体…
}

语法二:

function f_name () {
…函数体…
}

语法三:

f_name (){
…函数体…
}

函数使用

函数的定义和使用:

  • 可在交互式环境下定义函数

  • 可将函数放在脚本文件中作为它的一部分

  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行;

调用:给定函数名
函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

函数返回值

函数有两种返回值:

函数的执行结果返回值:
(1) 使用echo或printf命令进行输出
(2) 函数体中调用命令的输出结果

函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:

  • return  从函数中返回,用最后状态命令决定返回值

  • return 0  无错误返回。

  • return 1-255  有错误返回

使用函数文件和创建函数

使用函数:可以将经常使用的函数存入函数文件,然后将函数文件载入shell。

  1. 文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main

  2. 一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数。

  3. 若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件。、

创建函数文件

函数文件示例:

$cat functions.main
#!/bin/bash
#functions.main
findit()
{
if [ $# -lt 1 ] ; then
echo "Usage:findit file"
return 1
fi
find / -name $1 –print
}

载入函数

意义: 函数文件已创建好后,要将它载入shell

定位函数文件并载入shell的格式:

.  filename 或 source filename
注意:此即<点> <空格> <文件名>
这里的文件名要带正确路径

 示例:上例中的函数,可使用如下命令:
$ . functions.main

检查载入函数 set命令

使用set命令检查函数是否已载入。set命令将在shell中显示
所有的载入函数。

删除shell函数

现在对函数做一些改动。首先删除函数,使其对shell不可用。使用unset命令完成此功能.
命令格式为:
unset  function_name

 实例:
$unset findit

再键入set命令,函数将不再显示

函数变量

变量作用域:

  • 环境变量:当前shell和子shell有效

  • 本地变量:只在当前shell进程有效,为执行脚本会启动

  • 专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数。

  • 局部变量:函数的生命周期;函数结束时变量被自动销毁
    注意:如果函数中有局部变量,如果其名称同本地变量,使
    用局部变量。

在函数中定义局部变量的方法

local NAME=VALUE

函数的递归

  • 函数直接或间接调用自身

  • 注意递归层数

例:计算一个正整数的阶乘

#!/bin/bash
#:0!=1,n!=(n-1)!×n
fact() {
if [ $1 -eq 0  -o  $1 -eq 1 ] ; then
echo 1
else
echo "$[$1*$(fact $[$1-1])]"
fi
}
fact $1

例:波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)写一个函数,求n阶斐波那契数列

#/bin/bash
#qzx
#
fun(){

if [ $1 -eq 0 ]
then
        echo "0"
elif [ $1 -eq 1 ]
then
        echo "1"
else
        echo "$[$(fun $[$1-2])+$(fun $[$1-1])]"
fi

}
fun $1

综合性练习

1、编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running…”
如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME
is stopped…”
其中:SCRIPT_NAME为当前脚本名
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理

#/bin/bash
#qzx
#
fun (){
if [[ -f /var/lock/subsys/SCRIPT_NAME ]]
then
        a=1
else
        a=0
fi
}

fun1(){
fun
if [ $a -eq 1 ]
then
        echo "已经启动无需重复操作"
else
        touch /var/lock/subsys/SCRIPT_NAME
        echo "启动成功"
fi
}

fun2(){
fun
if [ $a -eq 0 ]
then
        echo "已经停止成功,无需重复操作"

else
        rm -rf  /var/lock/subsys/SCRIPT_NAME
        echo "停止成功"
fi
}

fun3(){
fun
[[ $a -eq 1 ]] && echo ""$0" is running..." || echo ""$0" is stopped..." 

}

PS3="请输入 start stop restart status 所对应的数字  "
select str in start stop restart status
do
        case $str in
        start)
                fun1
                ;;
        stop)
                fun2
                ;;
        restart)
                fun1;fun2
                ;;
        status)
                fun3
                ;;
        *)
                echo "您输入的不正确。。"
                break
                ;;
        esac
done

2、编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称;
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下;
如:/bin/bash ==/mnt/sysroot/bin/bash
/usr/bin/passwd ==/mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下:
如:/lib64/ld-linux-x86-64.so.2 ==/mnt/sysroot/lib64/ldlinux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命
令,并重复完成上述功能;直到用户输入quit退出

#/bin/bash
#qzx
#

funku(){
for meihangdu in `ldd $quanming | sed -r -n 's@.*(/.*) \(.*$@\1@p'`
do
    local lujing=`dirname $meihangdu`
    mkdir -p /mnt/sysroot/$lujing &>/dev/null
    cp -p $meihangdu  /mnt/sysroot/$lujing &>/dev/null
done
}
while true
do
read -p "请输入一个可执行的命令名称 " cmds
which $cmds
if [ $? -eq 0 ]
then
    quanming=`which $cmds | tail -1`
    lujing=`dirname $quanming`
    mkdir -p /mnt/sysroot/$lujing &>/dev/null
    cp -p $quanming  /mnt/sysroot/$lujing &>/dev/null

    funku
elif [[ $cmds == "quit" ]]
then
    break
else 
    echo "您输入的命令不对"
fi    

done

原创文章,作者:qzx,如若转载,请注明出处:http://www.178linux.com/38543

(0)
qzxqzx
上一篇 2016-08-21
下一篇 2016-08-21

相关推荐

  • grep与正则表达式

    文本处理中比较强悍的三个工具是:grep、sed、awk。 其中grep主要作用是对于用户给出“模式”对文本逐行进行匹配检查,然后进行打印。   模式:由正则表达式字符及文本字符编写的过滤条件 格式:grep [选项] [正则表达式字符]  文件  常用的选项:     -v:反向查找,显示没有被匹配的的行 …

    Linux干货 2016-08-08
  • 第三周作业

    第三周作业

    Linux干货 2017-12-19
  • Linux之bash shell脚本编程入门篇(一)

    什么是bash shell脚本编程? 答:Linux里面有多种shell,而CentOS和redhat的默认shell是bash shell。至于shell脚本,这个跟windows操作系统里面的批处理文件有点像(.bat的文件)。不知道大家还是否记得Linux的哲学思想吗?其中有那么两点点:由众多目的的单一应用程序组成:一个程序只做一件事,且做好;组合目的…

    Linux干货 2016-08-15
  • rpm及yum

    库文件 查看二进制程序所依赖的库文件: ldd /PATH/TO/BINARY_FILE [root@localhost ~]# ldd /bin/bash 管理及查看本机装载的库文件: ldconfig /sbin/ldconfig -p:  显示本机已经缓存的所有可用库文件 名及文件路径映射关系 配置文件:/etc/ld.so.conf, /e…

    Linux干货 2016-08-29
  • M22 用户、组学习总结

    Linux系统的登录方式是通过账号和密码。每一个登录账号都有一个主组可能有附加组。Linux内的文件和目录都有所有者和属组,只有相应权限的账户可以对其进行操作,下面我对用户权限相关内容进行了总结。   1、  用户ID,每个用户具有相应的ID号码,主要分为两类:系统ID和用户ID,在Centos6中系统ID为1-499,用户ID >…

    2017-02-22