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

相关推荐

  • iptables基础概念

    iptables基本概念梳理 前言 netfilter/Iptables (其中包括netfilter和Iptables两个组件)组 成了Linux平台下的包过滤防火墙,它与大多数的Linux自带软 件一样,这个防火墙是免费提供的,它可以代替昂贵的企业级 防火墙来解决实际问题与实际方案,完成封包过滤,封包重定 向和网络地址转换等功能。 一、工作原理 数据包从…

    Linux干货 2016-12-19
  • N23-第五周博客作业

    1、显示当前系统上root、fedora或user1用户的默认shell。 [root@localhost ~]# cat /etc/passwd | egrep "^root\>" | cut -d: -f1,7 2、找出/etc/rc.d/init.d/functions文件中某单词后面跟一组小括号的行,形如:hell…

    Linux干货 2016-12-05
  • 配额-and-RAID

    配置配额系统 控制单个用户,或组在某磁盘上所能占的最大空间大小 配额是基于磁盘分区的,不基于文件 如何基于软件控制配额:          1、设置分区挂载选项          &nbsp…

    Linux干货 2016-08-29
  • Linux程序包管理(一)RPM使用

    Linux程序包管理 在早期我们使用源代码的方式安装软件时,都需要先把源程序代码编译成可执行的二进制应用程序,然后进行安装。意味着每次安装软件都需要经过 预处理 –> 编译 –> 汇编–> 链接, 这个复杂的过程。为简化安装步骤,程序提供商就在特定的系统上面编译好相关程序的安装文件并进行打包,提…

    Linux干货 2016-06-01
  • day06(8-3)作业-文件权限

    第一题、三种权限rwx对文件和目录的意义     权限对文件的意义         r(read):可以读取文件的内容,如读取文本文件的内容         w(write):可以编辑、新增或者是修改文件的内容,但不含有删除文件(…

    Linux干货 2016-08-04
  • IP地址之IPv6

    一、IPv6概述   目前使用的IP地址都是IPv4版本,之所以开发出IPv6版本主要是基于以下原因:     1、IPv4地址空间不足,分配不合理;     2、IPv4头部太过于复杂;     3、使用IPv4使得路由器及主机配置复杂;     4、对于IPv4重新…

    Linux干货 2016-01-13