shell脚本编程

bash的配置文件

用户登录系统后,会启动一个接口程序,也称shell程序,一般默认是bash,而这个程序的启动,需要读取配置文件,完成环境的配置,以便用户在该接口程序下方便的执行一系列操作

配置文件的种类:

根据配置文件中定义的内容,将配置文件分为两大类

profile类:为交互式登录的shell进程提供配置

用于定义环境变量,启动需要执行的命令或者脚本

  • /etc/profile: 全局设置

  • /etc/profile.d/*.sh

  • ~/.bash_profile: 个人设置(仅对当前用户有效)

bashrc类:为非交互式登录的shell进程提供配置

用于定义命令别名或本地变量

  • /etc/bashrc:全局设置
  • ~/.bashrc:个人设置

配置文件的加载顺序

交互式登录

直接通过某终端输入账号和密码后登录打开的shell进程;
使用su命令:su - USERNAME, 或者使用 su -l USERNAME执行的登录切换;

顺序:/etc/profile-->/etc/profile.d/*.sh-->/.bash_profile-->~/.bashrc-->/etc/bashrc

非交互式登录

su USERNAME执行的登录切换;
图形界面下打开的终端;
运行脚本

顺序:~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh

  • 命令行中定义的特性,例如变量和别名作用域为当前shell进程的生命周期;
  • 配置文件定义的特性,只对随后新启动的shell进程有效;

让通过配置文件定义的特性立即生效:
(1) 通过命令行重复定义一次;
(2) 让shell进程重读配置文件;

~]# source /PATH/FROM/CONF_FILE
~]# . /PATH/FROM/CONF_FILE


shell脚本编程

过程式编程,解释运行,依赖于外部程序文件运行

编程语言的分类:根据运行方式

  • 编译运行:源代码 --> 编译器 (编译)--> 程序文件;
  • 解释运行:源代码 --> 运行时启动解释器,由解释器边解释边运行;

根据其编程过程中功能的实现是调用库还是调用外部的程序文件:

编程模型:过程式编程语言,面向对象的编程语言

过程式:以指令为中心来组织代码,数据是服务于代码;

  • 顺序执行
  • 选择执行
  • 循环执行
    代表:C,bash

对象式:以数据为中心来组织代码,围绕数据来组织指令;
类(class):实例化对象,method;
代表:Java, C++, Python

数据类型:字符型,数值型
弱类型:字符型

shell脚本格式:

脚本文件的第一行,顶格:给出shebang(解释器路径),用于指明解释执行当前脚本的解释器程序文件

常见的解释器:
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

注意:代码需要加注释,如空白行,缩进,所有#开头的为注释行

文本编程器:nano
行编辑器:sed
全屏幕编程器:nano, vi, vim

shell脚本是命令的堆积,但很多命令不具有幂等性,需要用程序逻辑来判断运行条件是否满足,以避免其运行中发生错误;

运行脚本:

  • 赋予执行权限,并直接运行此程序文件;
    chmod +x /PATH/TO/SCRIPT_FILE
    /PATH/TO/SCRIPT_FILE

  • 直接运行解释器,将脚本以命令行参数传递给解释器程序;
    bash /PATH/TO/SCRIPT_FILE

注意:脚本中的空白行会被解释器忽略;
脚本中,除了shebang,余下所有以#开头的行,都会被视作注释行而被忽略;此即为注释行;
shell脚本的运行是通过运行一个子shell进程实现的;

练习1:写一个脚本,实现如下功能;
(1) 显示/etc目录下所有以大写p或小写p开头的文件或目录本身
(2) 显示/var目录下的所有文件或目录本身,并将显示结果中的小写字母转换为大写后显示
(3) 创建临时文件/tmp/myfile.XXXX

#!/bin/bash
ls -d /rtc/[pP]*
ls -d /var/* | 'a-z' 'A-Z'
mktemp /tmp/myfile.XXXX

ctrl+o:保存
ctrl+x:退出

脚本的状态返回值:

默认是脚本中执行的最后一条件命令的状态返回值

[root@centos7 ~]# id usr3 &> /dev/null && exit 0 || useradd usr3
logout
There are stopped jobs.
useradd: user 'usr3' already exists

自定义状态退出状态码:
exit [n]:n为自己指定的状态码;
注意:shell进程遇到exit时,即会终止,因此,整个脚本执行即为结束;

shell脚本的参数传递

位置参数变量

执行脚本和执行命令一样,在脚本后加上多个参数,用空格隔开即可,例如myscript.sh argu1 argu2

引用方式:

\$1, \$2, ..., \${10}, ${11}, ...

例如:

~]#vim sum.sh
#!/bin/bash
#
echo $[$1+$2]
轮替:

shift [n]:位置参数轮替;

~]#vim shift.sh
#!/bin/bash
#
echo "First pos argu: $1"
shift
echo "Second pos argu: $1"
~]# bash shift.sh one two


~]#vim shift.sh
#!/bin/bash
#
echo "First and second pos argu: $1, $2"
shift 2
echo "Third pos argu: $1"
~]# bash shift.sh one two three              

练习:写一脚本,通过命令传递两个文本文件路径给脚本,计算其空白行数之和

~]# vim lines.sh
   #!/bin/bash
   #
   file1_lines=$(grep "^$" $1 | wc -l)
   file2_lines=$(grep "^$" $2 | wc -l)

   echo "Total blank lines: $[$file1_lines+$file2_lines]"    
~]# bash lines.sh /etc.fstab /etc/issue

特殊变量

符号 含义
$0 脚本文件路径本身
$# 脚本参数的个数
$* 所有参数
$@ 所有参数

shell脚本的用户交互

用户交互:通过键盘输入数据,从而完成变量赋值操作;
read[option] [name...]
-p“提示字符”
-t # 限定输入时间

#!/bin/bash
#
read -p "Enter a username: " name
[ -z "$name" ] && echo "a username is needed." && exit 2

read -p "Enter password for $name, [password]: " password
[ -z "$password" ] && password="password"

if id $name &> /dev/null; then
   echo "$name exists."
else
   useradd $name
   echo "$password" | passwd --stdin $name &> /dev/null
   echo "Add user $name finished."
fi            

shell脚本的语法检测

  • bash -n FILE,智能检测出语法错误,不能检测出逻辑错误
  • bash -x FILE 调试执行

bash脚本编程之算术运算: +,-,*,/, **, %

算术运算格式:

  • let VAR=算术运算表达式
  • VAR=$[算术运算表达式]
  • VAR=$((算术运算表达式))
  • VAR=$(expr $ARG1 $OP $ARG2)

注意:乘法符号在有些场景中需要使用转义符;

变量赋值
增强型赋值:

变量做某种算术运算后回存至此变量中;
let i=$i+#
let i+=#

如:+=,-=,*=, /=, %=

自增:
VAR=$[$VAR+1]
let  VAR+=1
let  VAR++
自减:
VAR=$[$VAR-1]
let  VAR-=1
let  VAR--

练习:
1、写一个脚本
计算/etc/passwd文件中的第10个用户和第20个用户的id号之和;

id1=$(head -10  /etc/passwd | tail -1  | cut  -d:  -f3)
id2=$(head -20   /etc/passwd | tail -1  | cut  -d:  -f3)

2、写一个脚本
计算/etc/rc.d/init.d/functions和/etc/inittab文件的空白行数之和;

grep "^[[:space:]]*$"   /etc/rc.d/init.d/functions | wc -l

3、编写脚本,实现自动添加三个用户,并计算三个用户的UID之和

#!/bin/bash
#
id_sum=0
[ $# -lt 3 ] && echo "请输入三个用户:" && exit 1
for i in $* ;do
useradd $i && echo "${i}创建成功"
id_num=$(id -u $i)
id_sum=$[${id_sum}+${id_num}]
done
echo "三个用户uid之和为: ${id_sum}"

[root@centos7 ~]# bash useradd.sh 1 2 3
useradd: invalid user name '1'
useradd: invalid user name '2'
useradd: invalid user name '3'
三个用户uid之和为: 6

常用测试条件及控制语句

条件测试语句

判断某需求是否满足,需要由测试机制来实现

如何编写测试表达式以实现所需的测试:

  • 执行命令,并利用命令状态返回值来判断;

    0:成功
    1-255:失败

  • 测试表达式

    test EXPRESSION
    [ EXPRESSION ]
    [[ EXPRESSION ]]

注意:EXPRESSION两端必须有空白字符,否则为语法错误;

bash的测试类型

数值测试:
符号 含义
-eq 是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于
字符串测试
符号 含义
== 是否等于
> 是否大于
< 是否小于
!= 是否不等于
=~ 左侧的字符串是否被右侧的patter所匹配
-z “string” 判断指定的字符创是否为空
-n “string” 判断指定的字符串是否不空

注意

  1. 字符创比较尽量定义在双括号中
  2. 比较对象是变量时,最好加双引号(变量为空时,该测试会出现问题)
文件测试:
  • 存在性测试
  • -a FILE
  • -e FILE

文件存在性测试
存在则为真,否则则为假

符号 含义
-b FILE 文件是否存在,且为块设备文件
-c FILE 文件是否存在,且为字符设备
-d FILE 文件是否存在,且为目录文件
-f FILE 文件是否存在,且为普通文件
-h FILE 是否存在,且为符号链接
-p FILE 是否存在,且为命名管道文件
-S FILE 是否存在,且为套接字文件

文件权限测试

符号 含义
-r FILE 是否存在,且对当前用户可读
-w FILE 是否存在,且对当前用户可写
-x FILE 是否存在,且对当前用户可执行

特殊权限测试

符号 含义
-g FILE 是否存在,且拥有sgid权限
-u FILE 是否存在,且拥有suid权限
-k FILE 是否存在,且拥有sticky权限
双目测试
符号 含义
FILE1 -ef FILE2 file1与file2是否为指向同一个文件系统的相同inode的硬链接
FILE1 -ne FILE2 file1是否新于file2
FILE1 -ot FILE2 file1是否旧于file2
文件是否有内容:

-s FILE:是否有内容;
时间戳:
-N FILE:文件自从上一次读操作后是否被修改过;

从属关系测试:
符号 含义
-O FILE 当前用户是否为文件的属主
-G FILE 当前用户是否属于文件的属组
组合测试条件

逻辑运算

  • 命令的逻辑运算

    COMMAND1 && COMMAND2

    COMMAND1 || COMMAND2

    !COMMAND

  • 表达式的逻辑运算

    EXPRESSION1 -a EXPRESSION2

    EXPRESSION1 -o EXPRESSION2

    !EXPRESSION

练习:将当前主机名称保存至hostName变量中;主机名如果为空,或者为localhost.localdomain,则将其设置为www.magedu.com

hostName=$(hostname)

[ -z "$hostName" -o "$hostName" == "localhost.localdomain" -o "$hostName" == "localhost" ] && 
hostname [www.magedu.com](http://www.magedu.com/)

控制语句

if选择执行语句
  • 单分支
if  测试条件; then
    代码分支
fi

例如:测试/tmp目录下是否有test文件,如果有则告知用户,已存在

source-shell
if [[ -a /tmp/test ]];then
    echo "test is existing"
fi
  • 双分支
if  测试条件; then
       条件为真时执行的分
else
       条件为假时执行的分支
fi

例如:检查/tmp目录下是否有名为test的普通文件,没有则创建

  source-shell
if [[ -f /tmp/test ]];then
        echo "test is existing"
        else
            touch /tmp/test
fi
  • 多分支
 if  测试条件1;then
       条件1为真时执行的分支
elif  测试条件2;then
       条件2为真时执行的分支
......
else
       上述条件都不满足时的执行分支
fi

注意:即便多个条件可能同时都能满足,分支只会执行中其中一个,首先测试为“真”

示例:脚本参数传递一个文件路径给脚本,判断此文件的类型;

#!/bin/bash
#
if [ $# -lt 1 ]; then
     echo "At least on path."
     exit 1
fi

if ! [ -e $1 ]; then
     echo "No such file."
     exit 2
fi

if [ -f $1 ]; then
      echo "Common file."
elif [ -d $1 ]; then
      echo "Directory."
elif [ -L $1 ]; then
      echo "Symbolic link."
elif [ -b $1 ]; then
      echo "block special file."
elif [ -c $1 ]; then
      echo "character special file."
elif [ -S $1 ]; then
      echo "Socket file."
else
      echo "Unkown."
fi    

注意:if语句可嵌套;

练习1:通过命令行参数给定一个用户名,判断其ID号是偶数还是奇数

#!/bin.bash
if [ $# -lt 1 ];then
echo "Must give a username."
fi
num=$(id -u $1)
let num2=$num%2
#echo "num is $num"
#echo "num2 is $num2"
if [ $num2 -eq 1 ];then
echo "the uid is "odd number
else
echo "the uid is even number"
fi

练习2:写一个脚本
(1) 传递一个参数给脚本,此参数为用户名;
(2) 根据其ID号来判断用户类型:
0: 管理员
1-999:系统用户
1000+:登录用户

#!/bin/bash
#
[ $# -lt 1 ] && echo "At least on user name." && exit 1

! id $1 &> /dev/null && echo "No such user." && exit 2

userid=$(id -u $1)

if [ $userid -eq 0 ]; then
     echo "root"
elif [ $userid -ge 1000 ]; then
     echo "login user."
else
     echo "System user."
fi                                        

练习3:写一个脚本
(1) 列出如下菜单给用户:
disk: show disks info;
mem: show memory info;
cpu: show cpu info;
( * ) quit;
(2) (2) 正确的选择则给出相应的信息;否则,则提示重新选择正确的选项;

#!/bin/bash
#
cat << EOF
(disk) show disks info
(mem) show memory info
(cpu) show cpu info
(*) QUIT
EOF

read -p "Your choice: " option

while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do
                    echo "cpu, mem, disk, quit"
                    read -p "Enter your option again: " option
done

if [[ "$option" == "disk" ]]; then
      fdisk -l /dev/[sh]d[a-z]
elif [[ "$option" == "mem" ]]; then
      free -m
elif [[ "$option" == "cpu" ]];then
      lscpu
else
      echo "Unkown option."
      exit 3
fi
case语句
case  $VARAIBLE(变量引用)  in  
PAT1)
     分支1
      ;;
PAT2)
     分支2
      ;;
...
*)
     分支n
      ;;
esac

case支持glob风格的通配符:

*:任意长度的任意字符;
?:任意单个字符;
[]:范围内任意单个字符;
a|b:a或b;

示例:写一个服务框架脚本
$lockfile, 值/var/lock/subsys/SCRIPT_NAME

(1) 此脚本可接受start, stop, restart, status四个参数之一;
(2) 如果参数非此四者,则提示使用帮助后退出;
(3) start,则创建lockfile,并显示启动;stop,则删除lockfile,并显示停止;restart,则先删除此文件再创建此文件,而后显示重启完成;status,如果lockfile存在,则显示running,否则,则显示为stopped.

#!/bin/bash
#
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog

case $1  in
start)
      if [ -f $lockfile ]; then
          echo "$prog is running yet."
      else
         touch $lockfile
         [ $? -eq 0 ] && echo "start $prog finshed."
      fi
      ;;
stop)
      if [ -f $lockfile ]; then
           rm -f $lockfile
           [ $? -eq 0 ] && echo "stop $prog finished."
      else
         echo "$prog is not running."
      fi
      ;;
restart)
      if [ -f $lockfile ]; then
           rm -f $lockfile
           touch $lockfile
           echo "restart $prog finished."
     else
           touch -f $lockfile
           echo "start $prog finished."
      fi
      ;;
status)
      if [ -f $lockfile ]; then
         echo "$prog is running"
     else
         echo "$prog is stopped."
      fi
      ;;
*)
     echo "Usage: $prog {start|stop|restart|status}"
     exit 1
esac

循环执行: 将一段代码重复执行0、1或多次;

进入条件:

for:列表元素非空;
while:条件测试结果为“真”
unitl:条件测试结果为“假”

退出条件:每个循环都应该有退出条件,以有机会退出循环;

for:列表元素遍历完成;
while:条件测试结果为“假”
until:条件测试结果为“真

for循环:

两种格式:

  • 遍历列表
  • 控制变量

遍历列表:

for  VARAIBLE  in  LIST; do
     循环体
done

进入条件:只要列表有元素,即可进入循环;
退出条件:列表中的元素遍历完成;

示例:

#!/bin/bash
#
for username in user21 user22 user23; do
    if id $username &> /dev/null; then
        echo "$username exists."
    else
        useradd $username && echo "Add user $username finished."
    fi
done

LISTT的生成方式:

(1) 直接给出;
(2) 整数列表
(a) {start..end}
(b) seq [start [incremtal]] last
(3) 返回列表的命令
(4) glob
(5) 变量引用
@,*
...

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

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

for i in {1..100}; do
            echo "\$sum is $sum, \$i is $i"
            sum=$[$sum+$i]
done

echo $sum

示例:判断/var/log目录下的每一个文件的内容类型

#!/bin/bash
#
for filename in /var/log/*; do
     if [ -f $filename ]; then
        echo "Common file."
elif [ -d $filename ]; then
        echo "Directory."
elif [ -L $filename ]; then
        echo "Symbolic link."
elif [ -b $filename ]; then
        echo "block special file."
elif [ -c $filename ]; then
        echo "character special file."
elif [ -S $filename ]; then
        echo "Socket file."
else
        echo "Unkown."
     fi                    
done
for循环的特殊用法:
for  ((控制变量初始化;条件判断表达式;控制变量的修正语句)); do
                循环体
            done

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

示例:求100以内所有正整数之和

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

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

echo "Sum: $sum."

示例:用for循环检测10.0.0.1/24网段存活的IP地址

#!/bin/bash
#
declare -i a=0
for a in {1..255};do
if ping -w 2 -c 2 10.0.0.$a &>/dev/null;then
echo "10.0.0.$a is up"
else
echo "10.0.0.$a is down"
fi
done
while循环:
while  CONDITION; do
     循环体
     循环控制变量修正表达式
done

进入条件:CONDITION测试为”真“
退出条件:CONDITION测试为”假“

示例:求100以内所有正整数之和

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

while [ $i -le 100 ]; do
     let sum+=$i
     let i++
done

echo $sum
循环控制语句:

continue:提前结束本轮循环,而直接进入下一轮循环判断

while  CONDITION1; do
          CMD1
                    ...
      if  CONDITION2; then
          continue
      fi
          CMDn
         ...
done

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

#
declare -i evensum=0
declare -i i=0

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

echo "Even sum: $evensum"   

break:提前跳出循环

while  CONDITION1; do
          CMD1
          ..
     if  CONDITION2; then
          break
     fi
done

创建死循环:

while true; do
          循环体
done

退出方式:
某个测试条件满足时,让循环体执行break命令;

示例:求100以内所奇数之和

#!/bin/bash
#
declare -i oddsum=0
declare -i i=1

while true; do
     let oddsum+=$i
     let i+=2
     if [ $i -gt 100 ]; then
           break
    fi

done
sleep命令:

delay for a specified amount of time
sleep NUMBER

练习:每隔3秒钟到系统上获取已经登录用户的用户的信息;其中,如果logstash用户登录了系统,则记录于日志中,并退出;

#!/bin/bash
#
while true; do
      if who | grep "^logstash\>" &> /dev/null; then
          break
      fi
          sleep 3
done

echo "$(date +"%F %T") logstash logged on" >> /tmp/users.log    
#!/bin/bash
#
until who | grep "^logstash\>" &> /dev/null; do
         sleep 3
         done

echo "$(date +"%F %T") logstash logged on" >> /tmp/users.log   
while循环的特殊用法(遍历文件的行):
while  read  VARIABLE; do
          循环体;
done  <  /PATH/FROM/SOMEFILE

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

示例:找出ID号为偶数的用户,显示其用户名、ID及默认shell

#!/bin/bash
#
while read line; do
    userid=$(echo $line | cut -d: -f3)
    username=$(echo $line | cut -d: -f1)
    usershell=$(echo $line | cut -d: -f7)

    if [ $[$userid%2] -eq 0 ]; then
          echo "$username, $userid, $usershell."
    fi
done < /etc/passwd        

示例:用while循环检测10.0.0.1/24网段存活的IP地址

#!/bin/bash
#
declare -i a=0
declare -i b=0
declare -i c=0

while [ $a -le 255 ];do
if ping -w 2 -c 2 10.0.0.$a &>/dev/null;then
echo "10.0.0.$a is up"
else
echo "10.0.0.$a is down"
fi
let a++
done
until循环:
until  CONDITION; do
     循环体
     循环控制变量修正表达式
done

进入条件:CONDITION测试为”假“
退出条件:CONDITION测试为”真“

示例:求100以内所有正整数之和

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

until [ $i -gt 100 ]; do
     let sum+=$i
     let i++
done

echo $sum            

练习:打印九九乘法表
1X1=1
1X2=2 2X2=4
1X3=3 2X3=6 3X3=9
外循环控制乘数,内循环控制被乘数;

#!/bin/bash
#
for j in {1..9}; do
      for i in $(seq 1 $j); do
            echo -n -e "${i}X${j}=$[${i}*${j}]\t"
      done
      echo
done    
#!/bin/bash
#
for ((j=1;j<=9;j++)); do
         for ((i=1;i<=j;i++)); do
                 echo -e -n "${i}X${j}=$[${i}*${j}]\t"
         done
         echo
done              

写一个脚本:
ping命令去查看172.16.1.1-172.16.67.1范围内的所有主机是否在线;在线的显示为up, 不在线的显示down,分别统计在线主机,及不在线主机数;
分别使用for, while循环实现。

#!/bin/bash
#
declare -i uphosts=0
declare -i downhosts=0

for i in {1..67}; do
     if ping -W 1 -c 1 172.16.$i.1 &> /dev/null; then
             echo "172.16.$i.1 is up."
             let uphosts+=1
     else
             echo "172.16.$i.1 is down."
             let downhosts+=1
      fi
done

echo "Up hosts: $uphosts, Down hosts: $downhosts."        
#!/bin/bash
#
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1

hostping() {
          if ping -W 1 -c 1 $1 &> /dev/null; then
                   echo "$1 is up."
                   return 0
          else
                   echo "$1 is down."
                   return 1
          fi
}
          while [ $i -le 67 ]; do
                hostping 172.16.$i.1
                [ $? -eq 0 ] && let uphosts++ || let downhosts++
                let i++
done

echo "Up hosts: $uphosts, Down hosts: $downhosts."                    

写一个脚本,实现:
能探测C类、B类或A类网络中的所有主机是否在线;

#!/bin/bash
#
cping() {
        local i=1
        while [ $i -le 254 ]; do
                if ping -W 1 -c 1 $1.$i &> /dev/null; then
                        echo "$1.$i is up"
                else
                        echo "$1.$i is down."
                fi
                let i++
done
}
bping() {
        local j=0
        while [ $j -le 255 ]; do
                cping $1.$j
                let j++
done
}
aping() {
        local x=0
        while [ $x -le 255 ]; do
                bping $1.$x
                let x++
done
}

函数:function

过程式编程:代码重用

  • 模块化编程
  • 结构化编程

把一段独立功能的代码当作一个整体,并为之一个名字;命名的代码段,此即为函数;

注意:定义函数的代码段不会自动执行,在调用时执行;所谓调用函数,在代码中给定函数名即可;
函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码;

语法风格一:

function  f_name  {
         ...函数体...
}

语法风格二:

f_name()  {
         ...函数体...
}

函数的生命周期:每次被调用时创建,返回时终止;
其状态返回结果为函数体中运行的最后一条命令的状态结果;

自定义状态返回值,需要使用:return
return [0-255]
0: 成功
1-255: 失败

示例:给定一个用户名,取得用户的id号和默认shell

#!/bin/bash
#
userinfo() {
       if id "$username" &> /dev/null; then
             grep "^$username\>" /etc/passwd | cut -d: -f3,7
       else
             echo "No such user."
       fi
}

username=$1
userinfo

username=$2
userinfo        

示例2:服务脚本框架(函数形式)

#!/bin/bash
#
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog

start() {
        if [ -f $lockfile ]; then
               echo "$prog is running yet."
        else
               touch $lockfile
               [ $? -eq 0 ] && echo "start $prog finshed."
        fi
}

stop() {
        if [ -f $lockfile ]; then
               rm -f $lockfile
               [ $? -eq 0 ] && echo "stop $prog finished."
        else
               echo "$prog is not running."
        fi
}
status() {
        if [ -f $lockfile ]; then
               echo "$prog is running"
        else
               echo "$prog is stopped."
        fi
}

usage() {
               echo "Usage: $prog {start|stop|restart|status}"
}

case $1 in
start)
        start ;;
stop)
        stop ;;
restart)
        stop
        start ;;
status)
        status ;;
*)
        usage
        exit 1 ;;
esac
函数返回值:

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

函数的退出状态码:
(1) 默认取决于函数体中执行的最后一条命令的退出状态码;
(2) 自定义:return

函数可以接受参数:

传递参数给函数:
在函数体中当中,可以使用$1,$2, ...引用传递给函数的参数;还可以函数中使用$*或$@引用所有参数,$#引用传递的参数的个数;
在调用函数时,在函数名后面以空白符分隔给定参数列表即可,例如,testfunc arg1 arg2 arg3 ...

示例:添加10个用户,添加用户的功能使用函数实现,用户名做为参数传递给函数

#!/bin/bash
#
# 5: user exists

addusers() {
         if id $1 &> /dev/null; then
                return 5
         else
                useradd $1
                retval=$?
                return $retval
         fi
}

     for i in {1..10}; do
                addusers ${1}${i}
                retval=$?
          if [ $retval -eq 0 ]; then
                echo "Add user ${1}${i} finished."
          elif [ $retval -eq 5 ]; then
                echo "user ${1}${i} exists."
          else
                echo "Unkown Error."
          fi
done
变量作用域:

局部变量:作用域是函数的生命周期;在函数结束时被自动销毁;
定义局部变量的方法:local VARIABLE=VALUE
本地变量:作用域是运行脚本的shell进程的生命周期;因此,其作用范围为当前shell脚本程序文件;

示例程序:

#!/bin/bash
#
name=tom

setname() {
       local name=jerry
       echo "Function: $name"
}

setname
echo "Shell: $name"
函数递归:

函数直接或间接调用自身

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

fact $1                    
斐波那契数列:1,1,2,3,5,8,13,21,...
#!/bin/bash
#
fab() {
      if [ $1 -eq 1 ]; then
              echo -n "1 "
      elif [ $1 -eq 2 ]; then
              echo -n "1 "
      else
              echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] "
       fi
}

       for i in $(seq 1 $1); do
              fab $i
      done
 echo

数组:

程序=指令+数据
数据:变量、文件

变量:存储单个元素的内存空间

数组:存储多个元素的连续的内存空间
数组名:整个数组只有一个名字;
数组索引:编号从0开始;
数组名[索引]
${ARRAY_NAME[INDEX]}

注意:bash-4及之后的版本,支持自定义索引格式,而不仅仅是0,1,2,...数字格式;
此类数组称之为“关联数组”

[root@promote ~]# world[us]="america"
[root@promote ~]# echo ${world[us]}
america
[root@promote ~]# world[uk]="united kingdom"
[root@promote ~]# echo ${world[uk]}
united kingdom
声明数组:
  • declare -a NAME:声明索引数组
  • declare -A NAME:声明关联数组
数组中元素的赋值方式:

(1) 一次只赋值一个元素
ARRAY_NAME[INDEX]=value

[root@promote ~]# animal[0]=pig
[root@promote ~]# animal[1]=dog
[root@promote ~]# echo $animal
pig
[root@promote ~]# echo ${animal[0]}
pig
[root@promote ~]# echo ${animal[1]}
dog

(2) 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

[root@promote ~]# weekday=("Monday" "Tuesday" "Wednesday")
[root@promote ~]# echo ${weekday[1]}
Tuesday

(3) 只赋值特定元素;
ARRAY_NAME=([0]="VAL1" [3]="VAL4" ...)
注意:bash支持稀疏格式的数组

[root@promote ~]# sword=([0]="Yitianjian" [3]="Longquan")
[root@promote ~]# echo ${sword[3]}
Longquan

(4) read -a ARRAY_NAME

[root@promote ~]# read -a Jiangsu
ljiawen wangchenlu lalala
[root@promote ~]# echo ${Jiangsu[2]}
lalala

引用数组中的元素:${ARRAY_NAME[INDEX]}
注意:引用时,只给数组名,表示引用下标为0的元素;

数组的长度(数组中元素的个数):

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

[root@promote ~]# echo ${#Jiangsu[*]}
3
[root@promote ~]# echo ${#animal[@]}
2

示例:生成10个随机数,并找出其中的最大值

#!/bin/bash
#
declare -a  rand
declare -i max=0

for i in {0..9}; do
       rand[$i]=$RANDOM
       echo ${rand[$i]}
       [ ${rand[$i]} -gt $max ] && max=${rand[$i]}
done

echo "MAX: $max"            

练习:写一个脚本
定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计其下标为偶数的文件中的行数之和

#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
    if [ $[$i%2] -eq 0 ]; then
          let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
    fi
done

echo "Lines: $lines."
引用数组中的所有元素:

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

数组元素切片:

${ARRAY_NAME[@]:offset:number}
offset:要路过的元素个数;
number:要取出的元素个数;省略number时,表示取偏移量之后的所有元素;

[root@promote ~]# files=(/etc/[Pp]*)
[root@promote ~]# echo ${files[*]}
/etc/pam.d /etc/passwd /etc/passwd- /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/python
[root@promote ~]# echo ${files[@]:2:3}
/etc/passwd- /etc/pkcs11 /etc/pki

向非稀疏格式数组中追加元素:
ARRAY_NAME[${#ARRAY_NAME[*]}]=

删除数组中的某元素:
unset ARRAY[INDEX]

关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([index_name1]="value1" [index_name2]="value2" ...)

bash的内置字符串处理工具:

字符串切片:

${var:offset:number}取字符串的子串;
取字符趾的最右侧的几个字符:${var: -length}

注意:冒号后必须有一个空白字符

[root@promote ~]# name=jerry
[root@promote ~]# echo ${name: 2: 2}
rr
[root@promote ~]# echo ${name: 2}
rry
[root@promote ~]# echo ${name: -4}
erry

基于模式取子串:

${var#*word}:其中word是指定的分隔符;
功能:自左而右,查找var变量所存储的字符串中,第一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;

[root@promote ~]# mypath="/etc/init.d/functions"
[root@promote ~]# echo ${mypath#*/}
etc/init.d/functions

${var##*word}:其中word是指定的分隔符;
功能:自左而右,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;

[root@promote ~]# mypath="/etc/init.d/functions"
[root@promote ~]# echo ${mypath##*/}
functions

${var%word*}:其中word是指定的分隔符;
功能:自右而左,查找var变量所存储的字符串中,第一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;

[root@promote ~]# mypath="/etc/init.d/functions"
[root@promote ~]# echo ${mypath%/*}
/etc/init.d

${var%%word*}:其中word是指定的分隔符;
功能:自右而左,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;

[root@promote ~]# mypath="/etc/init.d/functions"
[root@promote ~]# echo ${mypath%%/*}
[root@promote ~]# mypath="etc/init.d/functions"
[root@promote ~]# echo ${mypath%%/*}
etc

示例:

[root@promote ~]# url=http://www.magedu.com:80
[root@promote ~]# echo ${url##*:}
80
[root@promote ~]# echo ${url%%:*}
http

查找替换:

${var/PATTERN/SUBSTI}:查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

${var//PATTERN/SUBSTI}:查找var所表示的字符串中,所有被PATTERN所匹配到的字符串,并将其全部替换为SUBSTI所表示的字符串;

[root@promote ~]# userinfo="root:x:0:0:root admin:/root:/bin/chroot"
[root@promote ~]# echo ${userinfo/root/ROOT}
ROOT:x:0:0:root admin:/root:/bin/chroot
[root@promote ~]# echo ${userinfo//root/ROOT}
ROOT:x:0:0:ROOT admin:/ROOT:/bin/chROOT

${var/#PATTERN/SUBSTI}:查找var所表示的字符串中,行首被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

${var/%PATTERN/SUBSTI}:查找var所表示的字符串中,行尾被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

注意:PATTERN中使用glob风格和通配符;

[root@promote ~]# userinfo="root:x:0:0:root admin:/root:/bin/chroot"
[root@promote ~]# echo ${userinfo/%r??t/ROOT}
root:x:0:0:root admin:/root:/bin/chROOT

查找删除:

${var/PATTERN}:以PATTERN为模式查找var字符串中第一次的匹配,并删除
${var//PATERN}:以PATTERN为模式查找var字符串中所有的匹配,并删除
${var/#PATTERN:以PATTERN为模式查找var字符串中所有被行首匹配的,并删除
${var/%PATTERN}:以PATTERN为模式查找var字符串中所有被行尾匹配的,并删除

字符大小写转换:

${var^^}:把var中的所有小写字符转换为大写
${var,,}:把var中的所有大写字符转换为小写

[root@promote ~]# url=http://www.magedu.com:80
[root@promote ~]# echo ${url^^}
HTTP://WWW.MAGEDU.COM:80
[root@promote ~]# myurl=${url^^}
[root@promote ~]# echo $myurl
HTTP://WWW.MAGEDU.COM:80
[root@promote ~]# echo ${myurl,,}
http://www.magedu.com:80

变量赋值:

${var:-VALUE}:如果var变量为空,或未设置,那么返回VALUE;否则,则返回var变量的值;

[root@promote ~]# echo hi
hi
[root@promote ~]# echo $hi

[root@promote ~]# echo ${hi:-world}
world
[root@promote ~]# hi="america"
[root@promote ~]# echo ${hi:-world}
america

${var:=VALUE}:如果var变量为空,或未设置,那么返回VALUE,并将VALUE赋值给var变量;否则,则返回var变量的值;

[root@promote ~]# hi="america"
[root@promote ~]# echo ${hi:=world}
america
[root@promote ~]# unset hi
[root@promote ~]# echo ${hi:=world}
world
[root@promote ~]# echo $hi
world

${var:+VALUE}:如果var变量不空,则返回VALUE;
${var:?ERROR_INFO}:如果var为空,或未设置,那么返回ERROR_INFO为错误提示;否则,返回var值;

信号捕捉:

trap命令
格式:trap 'COMMAND' SIGNALS

列出信号:
trap -l
kill -l

[root@promote ~]# trap -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    

man 7 signal
解释每个信号的功用

常可以进行捕捉的信号:

HUP, INT

示例:

#!/bin/bash
#
declare -a hosttmpfiles
trap  'mytrap'  INT

mytrap()  {
         echo "Quit"
         rm -f ${hosttmpfiles[@]}
         exit 1
}

for i in {1..50}; do
        tmpfile=$(mktemp /tmp/ping.XXXXXX)
        if ping -W 1 -c 1 172.16.$i.1 &> /dev/null; then
              echo "172.16.$i.1 is up" | tee $tmpfile
        else
              echo "172.16.$i.1 is down" | tee $tmpfile
        fi
        hosttmpfiles[${#hosttmpfiles[*]}]=$tmpfile
        done

rm -f ${hosttmpfiles[@]}
SIGHUP、SIGQUIT、SIGTERM、SIGINTERRUPT的区别
  • SIGHUP: 无需关闭进程而让其重读配置
  • SIGQUIT: 终止进程,由QUIT字符(通常是Ctrl-)来控制,进程在因收到SIGQUIT退出时会产生core文件
  • SIGTERM: 程序结束(terminate)信号,该信号可以被阻塞和处理。通常用来要求程序自己正常退出
  • SIGNTERRUPT: 允许信号中断系统调用

在bash中使用ACSII颜色

\033[31m hello \033[0m

##m:
左侧#:
3:前景色
4:背景色
右侧#:颜色种类
1, 2, 3, 4, 5, 6, 7

#m:
加粗、闪烁等功能;

多种控制符,可组合使用,彼此间用分号隔开

示例:

dialog命令:可实现窗口化编程

各窗体控件使用方式

[root@promote ~]# dialog --msgbox hello 17 30

如何获取用户选择或键入的内容?
默认,其输出信息被定向到了错误输出流;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,352评论 0 5
  • 一.编程基本概念 编程逻辑处理方式: 顺序执行 循环执行 选择执行 shell编程:过程式、解释执行编程语言的基...
    楠人帮阅读 1,366评论 0 1
  • 什么是Shell脚本 示例 看个例子吧: !/bin/sh cd ~ mkdir shell_tut cd she...
    码农朱同学阅读 2,498评论 0 5
  • 一、什么是Shell脚本 示例 看个例子吧: 示例解释 第1行:指定脚本解释器,这里是用/bin/sh做解释器的 ...
    Rick617阅读 772评论 0 1
  • 第二章节一草一木,皆是北方 请回答我的一九九三 可怜的姑娘失去了母亲,因为某些原因不能说话,有语言障碍。 世事艰难...
    妍兮兮阅读 1,270评论 8 53