MENU

Shell 编程入门笔记



一. Shell能做什么

  1. 自动化批量系统初始化程序(Update,软件安装,时区设置,安全策略。。。。)
  2. 自动化批量软件部署程序(LAMP,LNMP/,omcat,LVS,Nginx)
  3. 管理应用程序(KVM,集群管理扩容,MySQL)
  4. 日志分析处理程序(PV,UV,状态统计,grep/awk)
  5. 自动化备份/恢复程序(MySQL完全/增量备份+Crond)
  6. 自动化管理程序(批量远程修改密码,软件升级,配置更新)
  7. 配合Zabbix信息采集
  8. 自动化信息采集及监控程序(收集系统/应用的状态信息:CPU/Mem/Disk/Net/TCP Status/Apache/MySQL)
  9. 自动化扩容(增加云主机,业务上线)
  10. 俄罗斯方块,打印图形,算法排序实现

二. Shell规范

1. 脚本命名

脚本通常以.sh结尾

2.执行方式

  1. sh

    sh test.sh
  2. bash

    bash test.sh
  3. 以路径方式执行(需要脚本有执行权限)

    chmod +x test.sh
    ./test.sh

3. 编写规范

第一行声明程序解释器,写明解释器程序所在路径

#!/usr/bin/bash

三. Bash Shell 基础特性

1. 命令和文件自动补齐

Tab键自动补齐

2. 命令历史记忆功能

  1. 上下键切换历史命令
  2. !number :执行第n条命令
  3. !string:执行最近一条以string开头的命令
  4. !$:上一条命令的最后一个参数
  5. !!:上一条命令
  6. Ctrl+R:搜索历史命令

3. 别名功能

  1. alias:查看当前Shell别名
  2. unalias:取消当前Shell别名

4. 快捷键

  • Ctrl+R:搜索历史命令
  • Ctrl+D:等于logout或exit,退出当前Shell
  • Ctrl+A:光标移动到行首
  • Ctrl+E:光标移动到行尾
  • Ctrl+L:清屏,相当于clear命令
  • Ctrl+U:删除或剪切光标之前的命令。
  • Ctrl+K:删除或剪切光标之后的内容。
  • Ctrl+S:暂停屏幕输出
  • Ctrl+Q:恢复屏幕输出
  • Ctl+C:强制中止当前的命令
  • Ctrl+Z:暂停命令,并放入后台

5. 前后台作业控制

  1. &:后台执行命令,终端关闭后任务结束
  2. nohup:后台执行命令,终端关闭后不终止
  3. screen:后台执行命令
  4. Ctrl+C:结束前台进程
  5. kill:结束指定进程
  6. bg/fg:进程转后台/前台

6. 输入输出重定向

  1. > :输出重定向到一个文件或设备 覆盖原来的文件
  2. \>!:输出重定向到一个文件或设备 强制覆盖原来的文件
  3. >>:输出重定向到一个文件或设备 追加原来的文件
  4. <:输入重定向到一个程序
  5. 2>:将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 b-shell
  6. 2>>:将一个标准错误输出重定向到一个文件或设备 追加到原来的文件
  7. 2>&1:将一个标准错误输出重定向到标准输出
  8. &>:将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件
  9. |&: 将一个标准错误 管道 输送 到另一个命令作为输入
  10. 标准输入;代码为 0 ;或称为 stdin ;使用的方式为 <
  11. 标准输出:代码为 1 ;或称为 stdout;使用的方式为 1>
  12. 错误输出:代码为 2 ;或称为 stderr;使用的方式为 2>

7. 管道

  1. |:上一条命令的输出输入给下一条命令
  2. tee:读取标准输入的数据,并将其内容输出成文件

8. 命令排序

  1. ;:顺序地独立执行各条命令, 彼此之间不关心是否失败, 所有命令都会执行
  2. &&: 顺序执行各条命令, 只有当前一个执行成功时候, 才执行后面的
  3. ||:顺序执行各条命令, 只有当前面一个执行失败的时候, 才执行后面的

9.通配符(元字符)

  1. *:匹配 0 个或匹配任意多个字符
  2. ?:匹配任意一个字符
  3. []:匹配括号中任意一个字符,如[abc] [a-z] [0-9] [a-zA-Z0-9] [^a-zA-Z0-9]
  4. ():在子Shell中执行,不会对当前环境造成影线
  5. {}:集合
  6. \:转义元字符

四. Shell变量

1. 什么是变量?

用一个特定的字符串去表示不固定的内容

2.变量的类型

2.1. 自定义变量

  1. 定义变量:变量名=变量值,变量名必须以字母或下划线开头
  2. 引用变量:$变量名${变量名}
  3. 查看变量:echo $变量名
  4. 取消变量:unset 变量名
  5. 作用范围:仅在当前Shell中有效

2.2. 系统环境变量

  1. 定义环境变量:

    • export 变量名=变量值
    • export 变量名:将自定义变量转换成环境变量
  2. 引用环境变量:$变量名${变量名}
  3. 查看环境变量:

    • echo $变量名
    • env:显示所有环境变量
  4. 取消环境变量:unset 变量名
  5. 作用环境范围:在当前Shell和子Shell中有效

2.3. 位置变量

$number:第n个参数作为变量值赋给变量

2.4. 预定义变量

  1. $0:脚本名
  2. $*:所有的参数
  3. $@:所有的参数
  4. $#:参数的个数
  5. $$:当前进程的PID
  6. $!:上一个后台进程的PID
  7. $?:上一个命令的返回值,0表示成功,非零表示失败

3. 变量赋值方式

  1. 显示赋值:变量名=变量值

    #示例
    ip=192.168.1.100
    school=“Tsinghua University”
    today1=`date +%F`
    today2=$(date +%F)
  2. 从键盘读入变量值 readdaima

    #示例:
    read 变量名
    read -p "提示信息:" 变量名
    read -t 5 -p "提示信息:" 变量名
    read -n 2 变量名

4. 定义或引用变量时注意事项

  1. "":弱引用

    #示例
    school=“Tsinghua University”
    echo "${school} is good"
    #输出:Tsinghua University is good
  2. '':强引用

    #示例
    school=“Tsinghua University”
    echo '${school} is good'
    #输出:${school} is good
  3. ``:命令替换,等价于$(),反引号中的Shell命令会被先执行

    #示例
    touch `data +%F`_file1.txt
    touch $(date +%F)_file2.txt

5. 变量的运算

5.1. 整数运算

  1. 方法一:expr

    #示例    + - \* / %
    #注意:星号不进行转义会报错
    #注意:运算符号左右各有一个空格
    expr 1 + 2
    expr $num1 + $num2
  2. 方法二:$(())

    #示例 + - * / %
    echo $(($num1+$num2))
    #变量名前的$符号可省略不写
    echo $((num1+num2))
    echo $((5-3*2))
    echo $(((5-3)*2))
    echo $((2**3))
    sum=$((1+2)) ; echo $sum
  3. 方法三:$[]

    #示例 + - * / %
    echo $[5+2]
    echo $[5**2]
  4. 方法四:let

    #示例
    let sum=2+3 ; echo $sum
    let i++ ; echo $i

5.2. 小数运算

#示例
echo "2*4" | bc
echo "2^4" | bc        #^代表取幂
echo "scale=2;6/4" | bc        #小数点后保留两位
awk 'BEGIN{print 1/2}'
echo "print 5.0/2" | python

5.3. 变量运算实例

  1. 计算内存使用量

    #!/usr/bin/bash
    mem_used=`free -m | grep '^Mem:' | awk '{print $3}'`
    mem_total=`free -m | grep '^Mem:' | awk '{print $2}'`
    mem_percent=$((mem_used*100/mem_total))
    echo "当前内存使用百分比为:$mem_percent %"
  2. ping主机

    #!/usr/bin/bash
    ip=192.168.0.100
    i=1
    while [ $i -le 5 ]
    do
        ping -c1 $ip &>/dev/null
        if [ $? -eq 0];then
            echo "$ip is up..."
        fi
        let i++
    done

6. 变量内容的删除和替换

  1. 变量内容删除

    #示例
    url=www.leeyiding.com
    echo ${url}            #打印变量值(标准查看)
    #输出:www.leeyiding.com
    echo ${#url}        #查看变量长度
    #输出:17
    
    ##字符串从前往后删除
    echo ${url#www.}    #删除字符串www.
    #输出:leeyiding.com
    echo ${url#*.}        #删除字符串www.(从前往后,最短匹配)
    #输出:leeyiding.com
    echo ${url##*.}        #删除字符串www.leeyiding.(从前往后,最长匹配/贪婪匹配)
    #输出:com
    
    ##字符串从后往前删除
    echo ${url%.com}    #删除字符串.com
    #输出:www.leeyiding
    echo ${url%.*}        #删除字符串.com(从后往前,最短匹配)
    #输出:www.leeyiding
    echo ${url%%.*}        #删除字符串.leeyiding.com(从后往前,最长匹配/贪婪匹配)
    #输出:www
  2. 变量内容的索引及切片

    #示例
    echo ${url:0:13}    #从第1个字符开始,共截取13个字符
    #输出www.leeyiding
    echo ${url:4:9}        #从第4个字符开始,共截取9个字符
    #输出leeyiding
    echo ${url:4}        #从第4个字符开始,一直截取到最后
    #输出leeyiding.com
  3. 变量内容的替换

    #示例
    echo ${url/com/cn}        #将com替换成cn
    #输出www.leeyiding.cn
    echo ${url/w/W}            #从前往后将第一个w替换成W
    #输出Www.leeyiding.com
    echo ${url//w/W}        #贪婪匹配,将所有w替换成W
    #输出WWW.leeyiding.com
  4. 变量的替代

    #示例1
    unset var1            #取消变量var1
    unset var2
    unset var3
    
    var2=                #赋给变量var2空值
    var3=333            #将值333赋给变量var
    echo ${var1-aaa}    #将值aaa赋给变量var1,输出aaa
    echo ${var2-bbb}    #变量var2为空值,无法替换,故输出空值
    echo ${var3-ccc}    #变量var3已有值,无法替换,故输出333
    
    #总结
    #${value-word}
    #变量没有被赋值:会使用“新的变量值”替代
    #变量有被赋值(包括空值):不会被替代
#示例2
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1:-aaa}    #输出aaa
echo ${var2:-bbb}    #输出bbb
echo ${var3:-ccc}    #输出333

#总结
#${value:-word}
#变量没有被赋值(包括空值):都会使用“新的变量值”替代
#变量有被赋值:不会被替代
#示例3
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1+aaa}    #输出空值
echo ${var2+bbb}    #输出bbb
echo ${var3+ccc}    #输出ccc

#总结
#${value:+word}
#变量被赋值(包括空值):都会使用“新的变量值”替代
#变量没有被赋值:不会被替代
#示例4
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1:+aaa}    #输出空值
echo ${var2:+bbb}    #输出空值
echo ${var3:+ccc}    #输出ccc

#总结
#${value:+word}
#变量被赋值:会使用“新的变量值”替代
#变量没有被赋值(包括空值):不会被替代
#示例5
unset var1
unset var2
unset var3
var2=
var3=333
echo ${var1=aaa}    #输出aaa
echo ${var2=bbb}    #输出空值
echo ${var3=ccc}    #输出ccc

#总结
#${value=word}
#变量被赋值或没有赋值:都会使用“新的变量值”替代
#变量为空值:不会被替代
#示例6
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1:=aaa}    #输出aaa
echo ${var2:=bbb}    #输出bbb
echo ${var3:=ccc}    #输出333

#总结
#${value:=word}
#变量没有被赋值(包括空值):都会使用“新的变量值”替代
#变量被赋值:不会被替代
#示例7
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1?aaa}    #报错,输出 bash: var1: aaa
echo ${var2?bbb}    #输出空值
echo ${var3?ccc}    #输出333

#总结
#${value?word}
#变量没有被赋值:报错
#变量被赋值(包括空值):不会被替代
#示例8
unset var1
unset var2
unset var3

var2=
var3=333
echo ${var1:?aaa}    #报错,输出 bash: var1: aaa
echo ${var2:?bbb}    #报错,输出 bash: var2: bbb
echo ${var3:?ccc}    #输出333

#总结
#${value:?word}
#变量没有被赋值(包括空值):报错
#变量被赋值:不会被替代

7. i++ 和++i

  1. 对变量值的影响

    #示例
    i=1
    let i++
    echo $i        #输出2
    
    j=1
    let ++j
    echo $j        #输出2
  2. 对表达式的值的影响

    #示例
    unset i
    unset j
    i=1
    j=1
    let x=i++    #先赋值,再运算
    let y=++j    #先运算,再赋值
    echo $i        #输出2
    echo $j        #输出2
    echo $x        #输出1
    echo $y        #输出2

8. 基础总结

8.1. 各种符号

  • ():在子Shell中执行命令
  • (()):数值比较、运算
  • $():命令替换,等用于``
  • $(()):整数运算
  • {}:集合
  • ${}:变量的引用、内容替换或替代
  • []:条件测试(文件测试、数值比较、字符串比较)
  • [[]]:在[]基础上支持正则表达式
  • $[]:整数运算

8.2. 执行脚本

执行方式权限执行Shell
./01.sh需要执行权限在子Shell中执行
bash 01.sh不需要执行权限在子Shell中执行
. 01.sh不需要执行权限在当前Shell中执行
source 01.sh不需要执行权限在当前Shell中执行

8.3 调试脚本

  • sh -n 02.sh:仅调试syntax error
  • sh -vx 0.2sh:以调试的方式执行,查询整个执行过程

四. Shell 条件测试

1. 表达式

  1. test 条件表达式
  2. [ 条件表达式 ]
  3. [[ 条件表达式 ]]
  4. C语言风格(())

    • ((1>2))
    • ((1==2))
    • ((1>=2))
    • ((1!=2))
    • ((`id -u`>0))
    • (($UID==0))

2. 条件测试参数

表达式描述测试类型
(EXPRESSION)表达式为真条件判断
! EXPRESSION表达式为假条件判断
EXPRESSION1 -a EXPRESSION2表达式1和表达式2都为真条件判断
EXPRESSION1 -o EXPRESSION2表达式1或表达式2其中一个为真条件判断
-n STRING字符串长度不为0字符串判断
-z STRING字符串长度为0字符串判断
STRING1 = STRING2字符串1和字符串2相同字符串判断
STRING1 != STRING2字符串1和字符串2不相同字符串判断
INTEGER1 -eq INTEGER2整数1与整数2相同整数判断
INTEGER1 -ge INTEGER2整数1大于等于整数2整数判断
INTEGER1 -gt INTEGER2整数1大于整数2整数判断
INTEGER1 -le INTEGER2整数1小于等于整数2整数判断
INTEGER1 -lt INTEGER2整数1小于整数2整数判断
INTEGER1 -ne INTEGER2整数1不等于整数2整数判断
-d DIR检查目录是否存在文件判断
-e FILE or DIR检查文件或目录是否存在文件判断
-f FILE检查文件是否存在文件判断
-r FILE检查文件是否存在并可读文件判断
-s FILE检查文件是否存在并非空文件判断
-w FILE检查文件是否存在并可写文件判断
-x FILE检查文件是否存在并可执行文件判断
-O FILE检查文件是否存在并属当前用户所有文件判断
-G FILE检查文件是否存在并且默认组与当前用户相同文件判断
FILE1 -nt FILE2检查文件1 是否比文件2 新文件判断
FILE1 -ot FILE2检查文件1 是否比文件2 旧文件判断

3. 示例

#判断目录是否存在
test -d /home
echo $?                #目录存在,输出0
test -d /home111    
echo $?                #目录不存在,输出1
#逻辑判断
[ 1 -lt 2 -a 5 -gt 10 ]; echo$?    #输出1
[ 1 -lt 2 -o 5 -gt 10 ]; echo$?    #输出0
[[ 1 -lt && 5 -gt 10 ]]; echo$?    #输出1
[[ 1 -lt || 5 -gt 10 ]]; echo$?    #输出0
#!/usr/bin/bash
#创建用户脚本 creat_user.sh
read -p "Please input a username:" user

if id $user &>/dev/null; then
    echo "user $user already exists"
else
    useradd $user
    if [ $? -eq 0 ]; then
        echo "$user is created."
    fi
fi
#!/usr/bin/bash
#根分区磁盘用量报警 disk_use.sh
disk_use=`df -Th | grep '/$' | awk '{print $(NF-1)}' | awk -F "%" '{print $1}'`
mail_user=root

if [ $disk_use -ge 90 ]; then
    echo "`data +%F-%H` disk:${disk_use}% is used" |mail -s "disk war..." $mail_user
fi
#!/usr/bin/bash
#批量添加用户 useradd1.sh
read -p "Please input number:" num
read -p "Please input prefix:" prefix

for i in `seq $num`        #seq创建从1到num序列
do
    user=$prefix$i
    useradd $user
    echo "123" | passwd --stdin $user &>/dev/null
    if [ $? -eq 0 ];then
        echo "$user is created"
    fi
done
#!/usr/bin/bash
#批量添加用户(严格模式) useradd2.sh
read -p "Please input number:" num
if [[ ! "$num" =~ ^[0-9]+$ || "$num" =~ ^0+$ ]];then    #正则匹配,判断$num是否是数字或为一个或多个0
    echo "Error number"
    exit
fi

read -p "Please input prefix:" prefix
if    [ -z "$prefix" ];then    #判断字符串是否为0
    echo "Error prefix"
    exit
fi

for i in `seq $num`        #seq创建从1到num序列
do
    user=$prefix$i
    useradd $user
    echo "123" | passwd --stdin $user &>/dev/null
    if [ $? -eq 0 ];then
        echo "$user is created"
    fi
done
#/usr/bin/local
#判断是否是命令 if_commanf.sh
command=/bin/date
if command -v $command &>/dev/null;then
    :                        #不执行任何命令,等同于true
else
    yum install $command
fi

五. 匹配模式:case

1. case语法结构

case $Var in
Pattern1)
    Command1
    ;;
Pattern2)
    Command2
    ;;
Patter
    Command3
    ;;
*)
    NoPatternCommand
esac

2. 示例

#!/usr/bin/bash
#多系统配置yum源 yum_config_case.sh
yum_server=192.168.0.100
os_version=`cat /etc/redhat-release | awk '{print $4}' | awk -F "." '{print $1"."$2}'`

[ -d /etc/yum.repos.d] || mkdri /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak &>/dev/null

case "$os_version" in
"7.3")
    cat > /etc/yum.repos.d/centos7u3.repo <<-EOF
    [centos7u3]
    name=centos7u3
    baseurl=ftp://$yum_server/centos7u3
    gpgcheck=0
    EOF
    echo "7.3 yum configure..."
    ;;
"6.8")
    curl -o /etc/yum.repos.d/centos6u8.repo ftp://$yum_server/centos6u8.repo
    ;;
"5.9")
    curl -o /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo
    ;;
*)
    echo "error"
esac
echo "Finish"
#!/usr/bin/bash
#删除用户 del_user.sh
read -p "Please input a username:" user

id $user &>/dev/null
if [ $? -ne 0 ];then
    echo "No user named $user"
    exit 1
fi

read -p "Are you sure you want delete $user ?[y/n]" action
#if[ "$action" = "y" -o "$action"= "Y" -o "$action" = "yes" -o "$action" = "YES" ];then
#    userdel -r $user
#    echo "$user is deleted"
#fi
case "$action" in
y|Y|yes|YES)
    userdel -r $user
    echo "$user is deleted"
    ;;
*)
    echo "error"
esac
#!/usr/bin/bash
#简易JumpServer跳板机系统 jump_server.sh
#创建管理用户jms
#useradd jms
#创建密钥
#ssh-keygen
#拷贝密钥
#ssh-copy-id 192.168.0.101
#ssh-copy-id 192.168.0.102
#ssh-copy-id 192.168.0.103
#脚本添加进.bash——profile
#/home/jms/jump_server.sh
trap "" HUP INT OUIT TSTP    #捕捉信号,禁止退出
web1=192.168.0.101
web2=192.168.0.102
mysql1=192.168.0.103
clear

while:
do
    cat << -EOF
    +-----------------------------+
    |    Jump_server                  |
    |    1. web1                      |
    |    2. web2                      |
    |    3. mysql1                  |
    +-----------------------------+
    EOF
    echo -en "\e[1;32input number: \e[0m"
    read num
    case "$num" in
    1)
        ssh jms@$web1
        ;;
    2)
        ssh jms@$web2
        ;;
    3)
        ssh jms@$mysql1
        ;;
    "")
        ;;
    *)
        echo "error"
    esac
done
#!/usr/bin/bash
#简易系统工具箱 system_manage.sh
menu() {
    cat <<-EOF
    #################################
    #        h. help                    #
    #        f. disk partition        #
    #        d. filesystem mount        #
    #        u. system load            #
    #        q. exit                    #
    #################################
    EOF
}
menu

while true
do
    read -p "Please input[h for help]: " action
    case "$action" in
    h)     clear; menu;;
    f)     fdisk -l;;
    d)     df -Th;;
    m)     free -m;;
    u)     uptime;;
    q)     break;;
    "")    ;;
    *)    echo "error"
    esac
done

六. 流程控制:if

1. 结构

#单分支结构
if 条件测试
then 命令序列
fi

#双分支结构
if 条件测试
then 命令序列
else 命令序列
fi

#多分支结构
if 条件测试1
then 命令序列1
elif 条件测试2
then 命令序列
elif 条件测试3
then 命令序列
else 命令序列
fi

2. 示例

#!/usr/bin/bash
#安装Apache install_apache01.sh
ping c1 wwww.baidu.com &>/dev/null
if [ $? -ne 0 ];then
    echo "connect unreachable"
    exit
fi

yum -y install httpd
systemstl start httpd
systemstl enable httpd
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
setenforce 0
#!/usr/bin/bash
#安装Apache install_apache02.sh
gatewau=192.168.0.1

ping c1 wwww.baidu.com &>/dev/null
if [ $? -eq 0 ];then
    yum -y install httpd
    systemstl start httpd
    systemstl enable httpd
    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https
    firewall-cmd --reload
    sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
    setenforce 0
    #测试
    curl http://127.0.0.1 &>/dev/null
    if [ $? eq 0 ];then
        echo "Apache is OK!"
    fi
elif ping -c1 $gateway &>/dev/null;then
    echo "Check DNS..."
else
    echo "check ip address!"
fi
#!/usr/bin/bash
#多系统配置yum源 yum_config.sh

os_version=`cat /etc/redhat-release | awk '{print $4}' | awk -F "." '{print $1"."$2}'`
yum_server=192.168.0.100

[ -d /etc/yum.repos.d] || mkdri /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak &>/dev/null
if ["$os_version" = "7.3" ];then
    cat > /etc/yum.repos.d/centos7u3.repo <<-EOF
    [centos7u3]
    name=centos7u3
    baseurl=ftp://$yum_server/centos7u3
    gpgcheck=0
    EOF
    echo "7.3 yum configure..."
elif ["$os_version" = "6.8" ];then
    curl -o /etc/yum.repos.d/centos6u8.repo ftp://$yum_server/centos6u8.repo
elif ["$os_version" = "5.9" ];then
    curl -o /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo
fi

七. Shell 循环:for

1. 语法结构

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

2. 示例

#!/usr/bin/bash
#探测局域网主机
>ip.txt
for i in {2..254}
do
    {
        ip=192.168.0.$i
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
            echo "$ip" | tee -a ip.txt
        fi
    }&
done
wait
echo "finishi...."
#!/usr/bin/bash
#批量创建用户
while true
do
    read -p "Please enter prefix & pass & num: " prefix pass num
    printf"user information:
    ----------------------
    user prefix: $prefix
    user password: $pass
    user number: $num
    ----------------------
    "
    read -p "Are you sure?[y/n]" action
    if ["$action" = "y" ];then
        break
    fi
done

for i in `seq -w $num`
do 
    user=$prefix$i
    id $user &>/dev/null
    if [ $? -eq 0 ];then
        echo "user $user already exists"
    else
        useradd $user
        echo "pass" | passwd --stdin $user &>/dev/null
        if [ $? -eq 0 ];then
            echo "$user is created."
        fi
    di
done
#!/usr/bin/bash
#批量从文件读入用户名,创建用户
pass=123

if [ $# -eq 0 ];then
    echo "usage: 'basename $0' file"
    exit 1
fi
if [ ! -f $1 ];then
    echo "error file"
    exit 2
fi

for user in `cat $1`
do
    id $user &>/dev/null
    if [ $? -eq 0 ];then
        echo "user $user already exists"
    else
        useradd $user
        echo "$pass" | passwd --stdin $user &>/dev/null
        if [ $? -eq 0 ] ;then
            echo "$user is created."
        fi
    fi
done
#!/usr/bin/bash
#批量从文件读入用户名和密码,创建用户

if [ $# -eq 0 ];then
    echo "usage: 'basename $0' file"
    exit 1
fi
if [ ! -f $1 ];then
    echo "error file"
    exit 2
fi

#重新定义分割符
IFS=$"\n"
for line in `cat $1`
do
    if [ ${#line} -eq 0 ];then
        continue
    fi
    user=`echo "$line" | awk '{print $1}'`
    pass=`echo "$line" | awk '{print $2}'`
    id $user &>/dev/null
    if [ $? -eq 0 ];then
        echo "user $user already exists"
    else
        useradd $user
        echo "$pass" | passwd --stdin $user &>/dev/null
        if [ $? -eq 0 ] ;then
            echo "$user is created."
        fi
    fi
done
#!/usr/bin/bash
#批量修改密码

read -p "Please enter a New Password: " pass

for ip in $(cat ip.txt)
do
    {
        ping -c1 -W1 $ip &>/dev/null
        if [ $? eq 0 ];then
            ssh $ip "echo $pass | passwd --stdin root"
            if [ $? -eq 0 ];then
                echo "$ip" >> ok_`data +%F`.txt
            else
                echo "$ip" >> fail_`data +%F`.txt
            fi
        else
            echo "$ip" >> fail_`data +%F`.txt
        fi
    }&
done
wait
echo "Everything is OK"
#!/usr/bin/bash
# 批量修改远程主机配置文件
for ip 'cat ip.txt'
do
    {
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
            ssh $ip "sed -ri '/^#UseDNS/cUseDNS no' /etc/ssh/sshd_config"
            ssh $ip "systemctl stop firewalld; systemctl disable firewalled"
        fi
    }&
done
wait

八. Shell循环:while until

1. 语法结构

#while
#当条件测试成立(条件测试为真),执行循环体
while 条件测试
do
    循环体
done

#until
#当条件测试成立(条件测试为假),执行循环体
until 条件测试
do
    循环体
done

2. 示例

#!/usr/bin/bash
#while循环读取文件用户名,批量创建用户
while read user
do
    id $user &>/dev/null
    if [ $? -eq 0 ] ;then
        echo "user $user already exista"
    else
        useradd $user
        if [ $? -eq 0 ];then
            echo "$user is created."
        fi
    fi
done < user.txt
#!/usr/bin/bash
#读取文件用户名和密码,批量创建用户
while read line
do
    if [ ${#line} -eq 0];then
        continue
    fi
    user=`echo "$line" | awk '{print $1}'`
    pass=`echo "$line" | awk '{print $2}'`
    id $user &>/dev/null
    if [ $? -eq 0 ] ;then
        echo "user $user already exista"
    else
        useradd $user
        echo "$pass" | passwd --stdin $user &>/dev/null
        if [ $? -eq 0 ];then
            echo "$user is created."
        fi
    fi
done < user.txt
#!/usr/bin/bash
#while测试远程主机连接
ip=192.168.0.100
while ping -c1 -W1 $ip &>/dev/null
do
    sleep 1
done
echo "$ip is down!"
#!/usr/bin/bash
#until测试远程主机连接
ip=192.168.0.100
until ping -c1 -W1 $ip &>/dev/null
do
    sleep 1
done
echo "$ip is up!"

九. Shell 并发控制

1. 传统并发方式

在循环体中套用{}&可以将每个循环放入后台执行,达到处理并发的目的,但只是用于小规模的并发,且该并发是无控制的,无限制蔓延,导致每个并发的完成没有先后。

#!/usr/bin/bash
#测试远程主机连通
for i in {1..254}
do
    {
        ip=192.168.0.$i
        ping c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
             echo "$ip is up"
         else
             echo "$ip is down"
         fi
    }&
done
wait

2. fd和命名管道实现并发控制

2.1. 句柄概念

File Descriptors(FD,文件描述符或文件句柄)

ls /proc/$$/fd
#0    1    10    2    29

ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:24 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:24 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:24 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:24 2 -> /dev/pts/3

touch file1
exec 6<> file1        #创建句柄
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:32 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:32 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 2 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 6 -> /root/file1ls

echo "111" >> /proc/$$/fd/6
cat file1
#111

rm -rf /file1
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:33 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:33 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 2 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 6 -> /root/file1 (deleted)

cp /proc/$$/fd/6 file1
cat file1
#1111

exec 6<&-    #删除句柄
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:34 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:34 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:34 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:34 2 -> /dev/pts/3

2.2. 管道

  1. 匿名管道

    rpm -qa | grep bash
  2. 命名管道

    #使用命名管道,将内容传给另一终端
    
    mkfifo /tmp/fifo1
    file /tmp/fifo1
    #/tmp/fifo1: fifo (named pipe)
    
    #终端1
    tty
    #/dev/pts/0
    ls /dev > /tmp/fifo1
    #终端2
    tty
    #/dev/pts/1
    grep 'sda' /tmp/fifo1
    # sda sda1 sda2 sda3

2.3. 举例

#!/usr/bin/bash
#测试远程主机连通
thread=5
tmp_fifofile=/tmp/$$.fifo

mkfifo $tmp_fifofile    #创建管道文件
exec 8<> $tmp_fifofile    #以编号为8的文件句柄打开管道文件
rm  -f $tmp_fifofile    #删除管道文件

for i in `seq $thread`
do
    echo >&8
done

for i in {1..254}
do
    read -u 8    
    #读到编号为8的文件句柄执行本次循环,否则等待之前循环完成后
    #再执行本次循环,最大并发数量为变量thread的值
    {
        ip=192.168.0.$i
        ping c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
             echo "$ip is up"
         else
             echo "$ip is down"
         fi
         echo >&8    #代表本次循环执行完成,释放进程
    }&
done
wait
exec 8>&-        #释放文件句柄

十. Expect 处理交互命令

1. 介绍

借助expect可以处理交互式的命令,使之自动化完成

2. 安装

yum -y install expect

3. 语法结构

expect [option] [args]

参数

  • -c 从命令行执行expect脚本
  • -d 输出调试信息

相关命令

  • spawn:启动新的进程
  • send:用于向进程发送字符串
  • expect:从进程接收字符串
  • interact:允许用户交互
  • exp_contimue:匹配多个字符串在执行动作后的命令

4. 示例

#!/usr/bin/expect
#ssh非交互式连接远程主机

set ip [lindex $argv 0]     #接收第一个参数 
#set ip 192.168.0.100
set user root
set password centos
set timeout 5

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\r" ; exp_continue }
    "password:" { send "$password\r"}
}

expect "#"
send "useradd 123\r"
send "pwd\r"
send "exit\r"
expect eof    #结束交互
#!/usr/bin/expect
#scp非交互式传输文件

set ip [lindex $argv 0]
set user root
set password centos
set timeout 5

spawn scp -r /etc/hosts $user@$ip:/tmp

expect {
    "yes/no" { send "yes\r" ; exp_continue }
    "password:" { send "$password\r"}
}
expect eof
#!/usr/bin/bash
#批量推送公钥
>ip.txt
passwd=centos

#判断是否安装expect
rpm -q expect &>/dev/null
if [ &? -ne 0 ];then
    yum -y install expect &>/dev/null
    echo "expect is installed"
fi

#判断是否安装密钥
if [ ! -f ~/.ssh/id_rsa ];then
    ssh-keygen -P "" -f ~/.ssh/id_rsa    #非交互式创建密钥
fi
    
for i in {2..254}
do
    {
        ip=192.168.0.$i
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
            echo "$ip" >> ip.txt
            /usr/bin/expect     <<-EOF
                set timeout 10
                spawn ssh-copy-id $ip
                expect {
                    "yes/no" { send "yes\r" ; exp_continue }
                    "password:" { send "$password" }
                }
            expect eof
            EOF
        fi
    }&
done
wait
echo "Finish....."

十一. Shell数组变量

1. 普通数组

1.1. 概念

普通数组:只能使用整数作为数组索引

1.2. 定义数组

#方法一:一次赋一个值
数组名[下标]=变量值
array1[0]=pear
array1[1]=apple
array1[2]=orange
array1[2]=peach

#方法二:一次赋多个值
数组名=(变量1 变量2 变量3)
array2=(tom jack alice)
array3=(`cat /etc/passwd`)
array4=(`ls /var/ftp/Shell/for8`)
array5=(tom jack alice "bash shell")
colors=($red $blue $green $recolor)
array5=(1 2 3 4 5 6 7 "linux shell" [20]=puppet)

1.3. 查看数组

declare -a
#declare -a array1='([0]="pear" [1]="apple" [2]="orange" [3]="peach")'
#declare -a array2='([0]="tom" [1]="jack" [2]="alice")'

1.4. 访问数组元素

echo ${array1[0]}        #访问数组中第一个元素
echo ${array1[@]}        #访问数组中所有元素
echo ${array1[*]}        #访问数组中所有元素
echo ${#array1[@]}        #统计数组元素的个数
echo ${!array1[@]}        #获取数组元素的索引
echo ${array1[@]:1}        #从数组下标1开始
echo ${array1[@]:1:2}    #从数组下标1开始,访问两个元素

1.5. 遍历数组

通过数组元素的个数遍历

通过数组元素的索引进行遍历

1.6. 示例

#!/usr/bin/bash
#使用while遍历hosts文件
while
do
    hosts[++i]=$line
done </etc/hosts

for i in ${!hosts[@]}
do
    echo "$i: ${host[$i]}"
done
#!/usr/bin/bash
#使用for遍历hosts文件
OLD_IFS=$IFS
IFS=$'\n'
for    line in 'cat /etc/hosts'
do
    hosts[++1]=$line
done

for i in ${!hosts[@]}
do
    echo "$i: ${host[$i]}"
done
IFS=$OLD_IFS

2. 关联数组

2.1. 概念

普通数组:可以使用字符串作为数组索引

2.2. 定义关联数组

#声明关联数组变量
declare -A ass_array1
declare -A ass_array2

#方法一:一次赋一个值
数组名[下标]=变量值
ass_array1[index1]=pear
ass_array1[index2]=apple
ass_array1[index3]=orange
ass_array1[index4]=peach

#方法二:一次赋多个值
数组名=([索引1]=变量1 [索引2]=变量2 [索引3]=变量3)
array2=([name]=jack [age]=10 [sex]=male [height]=160)

2.3. 示例

#!/usr/bin/bash
#统计性别
declare -A sex
while
do
    type=`echo $line | awk '{print $2}'`
    let sex[$type]++
done < sec.txt

for i in ${!sex[@]}
do
    echo "$i: $sex[$i]"
done
#!/usr/bin/bash
#统计不同shell数量
declare -A shells
while read line
do
    type=`echo $line | awk -F ":" '{print $NF}'`
    let shells[$type]++
done < /etc/passwd

for i in ${!sheels[@]}
do
    echo "$i: ${shells[$i]}"
done 
#!/usr/bin/bash
#统计TCP连接状态

while :
do
    declare -A status
    unset satus
    type=`ss -an | grep :80 | awk '{print $2}'`
    for i in $type
    do
        let status[$i]++
    done

    for j in ${!status[@]}
    do
        echo "$j: ${status[$j]}"
    done
    sleep 1
    clear
done

十二. function函数的定义及调用

1. 函数的概念

完成特定功能的代码片段

在Shell中定义函数可以使用代码模块化,便于复用代码

函数必须先定义才能使用

  • 传参 $1$2
  • 变量 local
  • 返回值 return $?

2. 定义函数

#方法一
函数名(){
    函数要实现的代码
}
#方法二
function 函数名(){
    函数要实现的代码
}

3. 调用函数

函数名
函数名 参数1 参数2

4. 示例

#!/usr/bin/bash
#阶乘
function factorial() {
    factorial=1
    for((i=1;i<=$1;i++))
    do
        factorial=$[ $factorial * $i ]
    done
    echo "$1的阶乘是: $factorial"
}

factorial $1
#!/usr/bin/bash
#函数使用return输出
fun2()    {
    read -p "enter num: " num
    return $[2*$num]    #最大不超过255
}
fun2
echo "fun 2 return value is : $?"
#!/usr/bin/bash
#函数使用out输出
fun2()    {
    read -p "enter num: " num
    echo $[2*$num]
}
result=`fun2`    #函数执行结果返回给变量

echo "fun 2 return value is : $return"
#!/usr/bin/bash
#函数位置参数
if [ $# -ne 3 ];then
    echo "usage: `basename $0` par1 par2 par3"
fi

fun3() {
    echo "$(($1 * $2 * $3))"    #$1 $2 $3为函数位置参数
}

result=`fun3 $1 $2 $3`            #$1 $2 $3为脚本位置参数

echo "result is: $result"
#!/usr/bin/bash
#函数使用数组传参
num=(1 2 3 4 5)
#echo "${num[@]}"

array() {
    local factorial=1    #局部变量,函数内部生效
    for i in $*            #传入所有位置参数
    do
        factorial=$[$factorial * $i]
    done
    echo "$factorial"
}

array ${num[*]}
#!/usr/bin/bash
#函数输出数组变量
num=(1 2 3)

array() {
    local newarray=($*)
    local i
    for ((i=0;i<$#;i++))
    do
        newarray[$i]=$(( ${newarray[$i]} * 5 ))
    done
    echo "${newarray[*]}"
}
result=`array ${num[*]}`
echo ${result[*]}
#!/usr/bin/bash
#函数输出数组变量2
num=(1 2 3)

array() {
    local i
    local j
    local outarray=()
    for i in $*
    do
        newarray[j++]=$[$i*5]
    done
    echo "${outarray[*]}"
}
result=`array ${num[*]}`
echo ${result[*]}

十三. Shell程序内置命令

1. 概述

  • :
  • true
  • false
  • exit:退出整个程序
  • break:结束当前循环或跳出本层循环
  • continue:忽略本次循环剩余的代码,直接进行下一次循环
  • shift:使位置参数向左移动,默认移动1位,可以使用shift 2

2. 示例

#!/usr/bin/bash
for i in {A..D}
do
    echo -n "$i"
    for j in {1..9}
    do
        if [ $j -eq 5];then
            continue
        fi
        echo -n $j
    done
    echo
done
#!/usr/bin/bash
while [ $# -ne 0]
do
    let sum+=$1
    shift
done
echo "sum: $sum"


版权属于:LeeYD · Blog
本文标题:Shell 编程入门笔记
本文链接:https://www.leeyiding.com/archives/44/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 4.0 许可协议
若转载本文,请标明出处并告知本人

返回文章列表 文章二维码 打赏
本页链接的二维码
打赏二维码
添加新评论

打卡

已有 2 条评论
  1. misaka7690 misaka7690     Android Pie /   Google Chrome

    写得蛮好的,请问可以转载吗
    滴!访客卡!请上车的乘客系好安全带,现在是:Tue Mar 03 2020 12:32:45 GMT+0800 (中国标准时间)
    滴!访客卡!请上车的乘客系好安全带,现在是:Tue Mar 03 2020 12:32:47 GMT+0800 (中国标准时间)

    1. LeeYD LeeYD     Android /   Google Chrome

      @misaka7690你好,可以转载,请问评论邮箱是你常用邮箱吗,我将markdown原文发送给你。