3.52. 老男孩Shell编程实战

3.52.1. 什么是shell脚本

第一个脚本示例

#!/usr/bin/env bash
LPG_DIR=/var/log
ROOT_UID=0

#脚本需要使用root用户权限
if [ -$"$UID" -ne "$ROOT_UID" ]; then
  echo "Must be root to run this script."
  exit 1
fi

cd $LPG_DIR || {
  echo "Cannot change to necessary directory."
  exit 1
}

cat /dev/null>messages && {
   echo "Logs cleaned up."
   exit 0  # 退出之前返回0表示成功,返回1表示失
}

echo "Logs cleaned up fail."
exit 1

脚本开头(第一行)

#!/bin/sh

#!/bin/bash

提示:sh为bash的软链接,大多数情况下,脚本的开头使用“#!/bin/bash”和“#!/bin/sh”是没有区别的,但更规范的写法是在脚本的开头使用“#!/bin/bash”。

脚本的执行方式

1)bash script-name或sh script-name

2)path/script-name或./script-name:指在当前路径下执行脚本(脚本需要有执行权限)

chmod 755 path/script-name

3)source script-name或.script-name

使用source或“.”可以将san.sh自身脚本中的变量值或函数等的返回值传递到当前父Shell脚本father.sh中使用。这是它和其他几种方法最大的区别

使用的是父shell

4)sh<script-name或cat scripts-name|sh:同样适用于bash

结论:通过source或“.”加载执行过的脚本,由于是在当前Shell中执行脚本,因此在脚本结束之后,脚本中的变量(包括函数)值在当前Shell中依然存在,而sh和bash执行脚本都会启动新的子Shell执行,执行完后退回到父Shell。因此,变量(包括函数)值等无法保留。

# 通过管道符也能执行
cat oldboy.sh | bash

环境变量初始化与对应文件的生效顺序

/etc/profile —> /etc/profile.d –> $HOME/.bash_profile —-> $HOME/.bashrc —> /etc/bashrc

顺序如下

/etc/profile     #每个用户登录时都将启动此文件,存储环境变量,加载完/etc/profile再去加载/etc/profile.d/
/etc/profile.d/*.sh
$HOME/.bash_profile  #存储环境变量
$HOME/.bashrc
/etc/bashrc

交互式

/etc/profile——当用户在运行级别3登录系统时首先运行。
/etc/profile.d——当/etc/profile运行时,会调用该目录下的一些脚本。
$HOME/.bash_profile
$HOME/.bashrc——上述脚本的中一个运行后即调用此脚本。

非交互式

$HOME/.bashrc——如果此文件存在即被运行。
/etc/bashrc——将被$HOME/.bashrc调用运行。
/etc/profile.d——此目录下的脚本将被/etc/bashrc或/etc/bash.bashrc调用运行。

提示

如果希望在非登录Shell下也可读到设置的环境变量等内容,就需要将变量设定等写入

$HOME/.bashrc

/etc/bashrc

3.52.2. 初始化操作系统脚本

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
export PATH=$PATH:/bin:/sbin:/usr/sbin
# Require root to run this script.
if [ "$UID" != "0" ]; then
    echo "Please run this script by root."
    exit 1
fi

#define cmd var
SERVICE=`which service`
CHKCONFIG=`which chkconfig`

function mod_yum(){
#modify yum path
if [ -e /etc/yum.repos.d/CentOS-Base.repo ]
    then
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup&&\
    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo
 fi
 }

function close_selinux(){
#1.close selinux
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
#grep SELINUX=disabled /etc/selinux/config
setenforce 0 &>/dev/null
#getenforce
}

function close_iptables(){
#2.close iptables
/etc/init.d/iptables stop
/etc/init.d/iptables stop
chkconfig iptables off
}

function least_service(){
#3.least service startup
chkconfig|awk '{print "chkconfig",$1,"off"}'|bash
chkconfig|egrep "crond|sshd|network|rsyslog|sysstat"|awk '{print "chkconfig",$1,"on"}'|bash
export LANG=en
chkconfig --list|grep 3:on
}

function adduser(){
#4.add oldboy and sudo
if [ `grep -w oldboy /etc/passwd|wc -l` -lt 1 ]
then
    useradd oldboy
    echo 123456|passwd --stdin oldboy \cp /etc/sudoers /etc/sudoers.ori
    echo "oldboy" ALL=(ALL) NOPASSWD: ALL " >>/etc/sudoers
    tail -1 /etc/sudoers
    visudo -c &>/dev/null
fi
}


function charset(){
#5.charset config
cp /etc/sysconfig/i18n /etc/sysconfig/i18n.ori
echo 'LANG="zh_CN.UTF-8"' >/etc/sysconfig/i18n
source /etc/sysconfig/i18n
#echo $LANG
}

function time_sync(){
#6.time sync.
cron=/var/spool/cron/root
if [ `grep -w "ntpdate" $cron|wc -l` -lt 1 ]
then
    echo '#time sync by oldboy at 2010-2-1' >>$cron
    echo '*/5 * * * * /usr/sbin/ntpdate time.nist.gov >/dev/null 2>&1' >>$cron
    crontab -l
fi
}

function com_line_set(){
#7.command set.
if [ `egrep "TMOUT|HISTSIZE|ISTFILESIZE" /etc/profile|wc -l` -lt 3 ]
then
    echo 'export TMOUT=300' >>/etc/profile
    echo 'export HISTSIZE=5' >>/etc/profile
    echo 'export HISTFILESIZE=5' >>/etc/profile
    . /etc/profile
fi
}

function open_file_set(){
#8.increase open file.
if [ `grep 65535 /etc/security/limits.conf|wc -l` -lt 1 ]
then
    echo '*   -   nofile 65535 ' >>/etc/security/limits.conf
    tail -1 /etc/security/limits.conf
    fi
}

function set_kernel(){
#9.kernel set.
if [ `grep kernel_flag /etc/sysctl.conf|wc -l` -lt 1 ]
    then
    cat >> /etc/sysctl.conf <<-EOF
    #kernel_flag
    net.ipv4.tcp_fin_timeout = 2
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_keepalive_time = 600
    net.ipv4.ip_local_port_range = 4000 65000
    net.ipv4.tcp_max_syn_backlog = 16384
    net.ipv4.tcp_max_tw_buckets = 36000
    net.ipv4.route.gc_timeout = 100
    net.ipv4.tcp_syn_retries = 1
    net.ipv4.tcp_synack_retries = 1
    net.core.somaxconn = 16384
    net.core.netdev_max_backlog = 16384
    net.ipv4.tcp_max_orphans = 16384
    net.nf_conntrack_max = 25000000
    net.netfilter.nf_conntrack_max = 25000000
    net.netfilter.nf_conntrack_tcp_timeout_established = 180
    net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
    net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
    net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
EOF
    sysctl -p
    fi

}

function init_ssh(){
    cp /etc/ssh/sshd_config /etc/ssh/sshd_config.`date +"%Y-%m-%d_%H-%M-%S"`
    sed -i 's%#Port 22%Port 52113%' /etc/ssh/sshd_config
    sed -i 's%#PermitRootLogin yes%PermitRootLogin no%' /etc/ssh/sshd_config
    sed -i 's%#PermitEmptyPasswords no%PermitEmptyPasswords no%' /etc/ssh/ sshd_config
    sed -i 's%#UseDNS yes%UseDNS no%' /etc/ssh/sshd_config
    /etc/init.d/sshd reload &>/dev/null
}

function update_linux(){
#10.upgrade linux.
if [ `rpm -qa lrzsz nmap tree dos2unix nc|wc -l` -le 3 ]
then
    yum install lrzsz nmap tree dos2unix nc -y
    yum update -y
fi
}

function main() {
    mod_yum
    close_iptables
    close_selinux
    least_service
    adduser
    charset
    time_sync
    com_line_set
    open_file_set
    set_kernel
    init_ssh
    update_linux
}
main

3.52.3. 开发检测脚本

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
# set env
export PATH=$PATH:/bin:/sbin:/usr/sbin

[ "UID" != "0" ] && { echo "Please run this script by root." ;exit 1 }

# Source function library
. /etc/init.d/functions


function check_yum() {
    Base=/etc/yum.repos.d/CentOS-Base.repo
    if [ `grep aliyun $Base|wc -l` -ge 1 ]; then
        action "$Base config" /bin/true
    else
        action "$Base config" /bin/false
    fi
}

function check_selinux() {
    config=/etc/selinux/config
    if [ `grep "SELINUX=disabled" $config|wc -l` -ge 1  ]; then
        action "$config config" /bin/true
    else
        action "$config config" /bin/false
    fi
}

function check_service() {
    export LANG=en
    if [ `chkconfig|grep 3:on|egrep "crond|sshd|network|rsyslog|sysstat"|wc -l` -eq 5 ]
    then
        action "sys service init" /bin/true
    else
        action "sys service init" /bin/false
    fi
}


function check_open_file(){
    limits=/etc/security/limits.conf
    if [ `grep 65535 $limits|wc -l` -eq 1 ]
    then
        action "$limits" /bin/true
    else
        action "$limits" /bin/false
    fi
}

function main() {
    check_yum
    check_selinux
    check_service
    check_open_file
}

main

3.52.4. 利用Shell函数开发rsync服务启动脚本

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
. /etc/init.d/functions

function usage(){
    echo $"usage:$0 {start|stop|restart}"
    exit 1
 }


function start(){
    rsync --daemon
    sleep 1
    if [ `netstat -lntup|grep rsync|wc -l` -ge 1 ]
    then
        action "rsyncd is started." /bin/true
    else
        action "rsyncd is started." /bin/false
    fi
}

function stop(){
    killall rsync &>/dev/null
    sleep 2
    if [ `netstat -lntup|grep rsync|wc -l` -eq 0 ]
    then
        action "rsyncd is stopped." /bin/true
    else
        action "rsyncd is started." /bin/false
fi
}

function main(){
#    if [ $# -ne 1 ];then
#        usage
#    fi
#    if [ "$1" = "start" ]
#    then
#        start
#    elif [ "$1" = "stop" ]
#    then
#        stop
#    elif [ "$1" = "restart" ]
#    then
#        stop
#        sleep 1
#        start
#    else
#        usage
#    fi
    case "$1" in
    "start")
        start
       ;;
    "stop")
        stop
       ;;
    "restart")
        stop
        sleep 1
        start
       ;;
     *)
        usage
     ;;
    esac
}
main $*

3.52.5. 一个颜色打印示例脚本

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
RES='\E[0m'

usage() {
    echo "Usage: basename $0 {1|2|3|4}"
    exit 1
}

menu(){
cat <<END
1.apple
2.pear
3.banana
END
}

chose() {
    read -p "Pls input your choice: " fruit
    case "$fruit" in
        1)
            echo -e "${RED_COLOR}apple${RES}"
            ;;
        2)
           echo -e "${GREEN_COLOR}pear${RES}"
           ;;
        3)
            echo -e "${YELLOW_COLOR}banana${RES}"
            ;;
        *)
            usage
    esac
}

main() {
    menu
    chose
}

main

3.52.6. 结合case给输出的语句加颜色

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

plus_color(){
RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
PINK='\E[1;35m'
RES='\E[0m'

if [ $# -ne 2 ];then
    echo "Usage $0 content {red|yellow|blue|green|pink}"
    exit
fi

case "$2" in
red|RED)
    echo -e "${RED_COLOR}$1${RES}";;
green|GREEN)
    echo -e "${GREEN_COLOR}$1${RES}";;
yellow|YELLOW)
    echo -e "${YELLOW_COLOR}$1${RES}";;
blue|BLUE)
    echo -e "${BLUE_COLOR}$1${RES}";;
pink|PINK)
    echo -e "${PINK}$1${RES}";;
*)
    echo "Usage $0 content {red|yellow|blue|green|pink}"
    exit
esac

}
plus_color "I" red
plus_color "am" green
plus_color "hujianli" blue

3.52.7. while循环

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
# 每隔2s在屏幕上输出负载值
#while true
#do
#    uptime
#    sleep 2
#done

while [ 1 ]; do
    uptime >> /tmp/uptime.log
    usleep 2000000          #单位是微秒,其实也是两秒
done

3.52.8. 数组用法

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
array=(
hujianli1
hujianli2
hujianli3
hujianli4
)

for (( i = 0; i <${#array[*]}; i++ )); do
    echo "This is num $i,then content is ${array[$i]}"
done
echo "---------------------------"
echo "array len:${#array[*]}"

#This is num 0,then content is hujianli1
#This is num 1,then content is hujianli2
#This is num 2,then content is hujianli3
#This is num 3,then content is hujianli4
#---------------------------
#array len:4




#  C语言的for循环
array=(1 2 3 4 5)

for (( VAR = 0; VAR < ${#array[*]}; VAR++ )); do
    echo ${array[VAR]}
done



# 普通for循环
array=(1 2 3 4 5)
for VAR in ${array[*]} ; do
    echo $VAR
done


# 使用while循环打印数组元素
array=(1 2 3 4 5)
i=0
while ((i<${#array[*]}))
do
    echo ${array[i]}
    ((i++))
done

3.52.9. while循环按行读文件的方式总结

方式1:采用exec读取文件,然后进入while循环处理。

exec <FILE
sum=0
while read line do
    cmd
done

方式2:使用cat读取文件内容,然后通过管道进入while循环处理。

cat FILE_PATH|while read line
do
    cmd
done

方式3:在while循环结尾done处通过输入重定向指定读取的文件。

while read line
do
    cmd
done<FILE

3.52.10. 批量检查多个网站地址是否正常。

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
. /etc/init.d/functions
check_count=0
url_list=(
#<==定义检测的URL数组,包含多个URL地址。
http://blog.oldboyedu.com
http://blog.etiantian.org
http://oldboy.blog.51cto.com
http://10.0.0.7
)

function wait(){
    echo -n '3秒后,执行检查URL操作.';
    for ((i=0;i<3;i++))
    do
        echo -n ".";sleep 1
    done
    echo
}

function check_url() {
    wait
    for ((i=0; i<`echo ${#url_list[*]}`; i++))
    do
        wget -o /dev/null -T 3 --tries=1 --spider ${url_list[$i]} >/dev/null 2>&1
        if [ $? -eq 0 ]
            then
            action "${url_list[$i]}" /bin/true
        else
            action "${url_list[$i]}" /bin/false
        fi
    done
    ((check_count++))
}
main() {
    while true; do
        check_url
        echo "-----------check count:${check_count}-----------------"
        sleep 10
    done
}

main

3.52.11. 开发一个守护进程脚本,每30秒监控一次MySQL主从复制是否异常

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
CheckDb(){
    status=($(awk -F ':' '/_Running|_Behind/{print $NF}' slave.log))
    for((i=0;i<${#status[*]};i++))
        do
            count=0
        if [ "${status[${i}]}" != "Yes" -a "${status[${i}]}" != "0" ]
        then
            let count+=1
        fi
    done

    if [ $count -ne 0 ];then
        echo "mysql replcation is failed"
        return 1
    else
        echo "mysql replcation is sucess"
        return 0
    fi
}

main(){
    while true
    do
        CheckDb
        sleep 30
    done
}

main

3.52.12. Shell脚本调试技巧

使用echo命令调试

echo命令是最有用的调试脚本的工具之一。
一般应在可能出现问题的脚本的重要部分加入echo命令,
例如在变量读取或修改操作的前后加入echo命令,并紧挨着退出命令exit。

使用bash命令参数调试

sh [-nvx] scripts.sh

参数说明如下。


·-n:不会执行该脚本,仅查询脚本语法是否有问题,并给出错误提示。
·-v:在执行脚本时,先将脚本的内容输出到屏幕上,然后执行脚本,如果有错误,也会给出错误提示。
·-x:将执行的脚本内容及输出显示到屏幕上,这是对调试很有用的参数。跟踪sh脚本的执行过程。

利用“sh-x脚本名”调试的缺点可以用set-x命令来弥补,它可以缩小调试的作用域。

使用set命令调试部分脚本内容

set命令也可以用于辅助脚本调试。

以下是set命令常用的调试选项。
·set-n:读命令但并不执行。
·set-v:显示读取的所有行。
·set-x:显示所有命令及其参数。

其他调试Shell脚本的工具

(1)Shell调试工具:bashdb

Shell调试器bashdb是一个类似于GDB的调试工具,可以完成对Shell脚本的断点设置、单步执行、变量观察等许多功能,

(2)Shell调试工具:shellcheck

shellcheck是一个可检查sh/bash脚本和命令语法的小工具

3.52.13. Expect用法

在不使用密钥的情况下,实现非人为交互登录。

ssh.exp

#!/usr/bin/expect
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

set IP "192.168.1.9"
set USER "root"
set PASS "admin#123"
set timeout 30
spawn ssh $USER@$IP
expect {
"(yes/no)" {send "yes\r"; exp_continue}
"password:" {send "$PASS\r"}
}
expect "#"          # 判断上次输入结果中是否包含"password:"字符串,如果有则立即返回,向下执行,否则就一直等待,知道超时时间到
send "touch /root/hujianli001.txt\r"
send "ls /etc > /root/hujianli001.txt\r"
send "mkdir /tmp/hujianli001\r"
send "exit\r"
expect eof

执行

expect ssh.exp

对服务器批量管理(了解)

login.sh

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian
echo
for ip in `awk '{print $1}' /root/ip_pass.txt` ; do
    pass=`grep $ip /root/ip_pass.txt|awk '{print $2}'`
    expect /root/ssh2.exp $ip $pass
done

ip_pass.txt

192.168.1.9 admin#123

ssh2.exp

#!/usr/bin/expect
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

set IP [lindex $argv 0]
set USER "root"
set PASS [lindex $argv 1]
set timeout 30
spawn ssh $USER@$IP
expect {
"(yes/no)" {send "yes\r"; exp_continue}
"password:" {send "$PASS\r"}
}

expect "#"          # 判断上次输入结果中是否包含"password:"字符串,如果有则立即返回,向下执行,否则就一直等待,知道超时时间到

send "touch /root/hujianli001.txt\r"
send "ls /etc > /root/hujianli001.txt\r"
send "mkdir /tmp/hujianli001\r"
send "exit\r"
expect eof

3.52.14. 企业生产场景下的Expect案例

IP地址

主机名

角色

192.168.33.128

oldgirl

管理机

192.168.33.129

littleboy

被管理机1

192.168.33.130

oldboy

被管理机2

批量执行命令

1)实现Expect自动交互的脚本: sample1.exp

#!/usr/bin/expect

if { $argc != 2 } {
    puts "usage: expect $argv0 ip command"
    exit
}

set ip [lindex $argv 0]
set cmd [lindex $argv 1]
set password "123456"
#
spawn ssh root@$ip $cmd
expect {       "yes/no" {send "yes\r";exp_continue}
               "*password" {send "$password\r"}
}
expect eof

执行命令如下:

expect sample1.exp 192.168.33.128 uptime

2)利用Shell循环执行Expect脚本命令: sample2.sh

#!/bin/sh
if [ $# -ne 1 ]
then
    echo $"USAGE:$0 cmd"
    exit 1
fi

cmd=$1
for n in 128 129 130
do
    expect sample1.exp 192.168.33.$n "$cmd"
done

使用命令如下: sh sample2.sh uptime

如果遇到连接SSH反应慢的问题,请在所有被管理的机器上提前执行如下命令: sed -ir '13iUseDNS no\nGSSAPIAuthentication no\n' /etc/ssh/sshd_config  /etc/init.d/sshd restart

### 批量发送文件

1)实现Expect自动交互的脚本:

sample001.exp

 #!/usr/bin/expect
if { $argc != 3 } {
    puts "usage: expect $argv0 file host dir"
    exit
}

#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir [lindex $argv 2]
set password "123456" s
pawn scp -P22 -rp $file root@$host:$dir

expect { "yes/no" {send "yes\r";exp_continue}
            "*password" {send "$password\r"}
}

expect eof

使用方法

expect sample001.exp /etc/hosts 192.168.33.130 /home/oldboy

2)利用Shell循环执行Expect脚本命令: sample002.sh

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

if [ $# -ne 2 ]
then
    echo $"USAGE:$0 file dir"
    exit 1
fi

file=$1
dir=$2
for n in 128 129 130
do
    expect sample001.exp $file 192.168.33.$n $dir
done

将/server/scripts目录批量发送到所有服务器指定的/tmp目录:

sh sample002.sh /server/scripts /tmp/

批量执行Shell脚本

1)准备脚本文件:

cat /server/scripts/yum.sh
yum install httpd -y

2)使用范例sample002.sh的脚本发送要执行的脚本文件到所有机器:

sh sample002.sh /server/scripts/yum.sh /tmp/

3)使用范例sample2.sh的脚本远程连接所有服务器并远程执行脚本:

sh sample2.sh "source /tmp/yum.sh"

3.52.15. 自动化部署SSH密钥认证+ansible的项目实战

1)本地生成密钥对,代码如下:

ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1

2)开发Expect脚本自动化交互分发公钥到所有的服务器:

ansible.exp

#!/usr/bin/expect
if { $argc != 2 } { send_user "usage: expect expect.exp file host\n"
                           exit }
#define var

set file [lindex $argv 0]
set host [lindex $argv 1]
set password "123456"

#start exec command
spawn ssh-copy-id -i $file "-p 22 root@$host"
expect {  "yes/no" {send "yes\r";exp_continue}
                "*password" {send "$password\r"}
}
expect eof

3)开发Shell脚本循环执行Expect脚本:

sample003.sh

#!/usr/bin/env bash
#usage:xxx
#scripts_name:${NAME}.sh
# author:xiaojian

for n in 128 129 130
do
    expect ansible.exp ~/.ssh/id_dsa.pub 192.168.33.$n
done

操作过程如下:

rm -fr ~/.ssh/
ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1
sh sample003.sh

4)实现无密码且不需要Expect就可以批量管理: cat exec.sh

ssh 192.168.33.128 uptime
ssh 192.168.33.129 uptime
ssh 192.168.33.130 uptime

5)配合ansible(Python开发的基于SSH的批量管理工具)实现自动化运维管理。 首先安装ansible,命令如下:

yum install epel-release -y
yum install ansible -y

然后编辑ansible的主机配置文件hosts,添加主机组oldboy:

[root@oldboy ~]# tail -4 /etc/ansible/hosts
[oldboy]
192.168.33.128
192.168.33.129
192.168.33.130

接着使用ansible并借助Expect建立好的SSH密钥认证执行命令

ansible oldboy -m command -a "uptime"

3.52.16. 使用exec调用其他外部命令或脚本

exec调用后会自动退出

#!/bin/bash
#功能描述(Description):使用exec调用其他外部命令或脚本示例.
ls
echo
echo "----------------------"
exec ls


echo "te

3.52.17. fork子进程的示例.

#!/bin/bash
#功能描述(Description):fork子进程的示例.

#调用外部命令时会导致fork子进程.
sleep 5

#绝对路径或相对路径调用外部脚本时会导致fork子进程.
/root/tmp.sh
cd /root; ./tmp.sh

3.52.18. 使用函数与&后台进程实现多进程ping测试1.

#!/bin/bash
#Version:1.0
#功能描述(Description):使用函数与&后台进程实现多进程ping测试.

net="192.168.4"

multi_ping() {
    ping -c2 -i0.2 -W1 $1 &>/dev/null
    if [ $? -eq 0 ];then
        echo "$1 is up."
    else
        echo "$1 is down."
    fi
}

#通过循环反复调用函数并将其放入后台并行执行.
for i in {1..254}
do
    multi_ping $net.$i &
done

3.52.19. 使用函数与&后台进程实现多进程ping测试2.

#!/bin/bash
#Version:2.0
#功能描述(Description):使用函数与&后台进程实现多进程ping测试.
#使用wait命令等待所有子进程结束后再退出脚本.

net="192.168.4"

multi_ping() {
    ping -c2 -i0.2 -W1 $1 &>/dev/null
    if [ $? -eq 0 ];then
        echo "$1 is up."
    else
        echo "$1 is down."
    fi
}

#通过循环反复调用函数并将其放入后台并行执行.
for i in {1..254}
do
    multi_ping $net.$i &
done
wait

3.52.20. 控制进程数量的ping测试脚本

#!/bin/bash
#Version:3.0
#功能描述(Description):控制进程数量的ping测试脚本.
#使用wait命令等待所有子进程结束后再退出脚本.

num=10             #控制进程数量.
net="192.168.4"
pipefile="/tmp/multiping_$$.tmp"

multi_ping() {
    ping -c2 -i0.2 -W1 $1 &>/dev/null
    if [ $? -eq 0 ];then
        echo "$1 is up."
    else
        echo "$1 is down."
    fi
}

#创建命名管道文件,创建其文件描述符,通过重定向导入数据到管道文件中.
mkfifo $pipefile
exec 12<>$pipefile
for i in `seq $num`
do
    echo "" >&12 &
done

#通过循环反复调用函数并将其放入后台并行执行.
#成功读取命名管道中的数据后开启新的进程.
#所有内容读取完后read被阻塞,无法再启动新进程.
#等待前面启动的进程结束后,继续往管道文件中写入数据,释放阻塞,再次开启新的进程.
for j in {1..254}
do
    read -u12
    {
        multi_ping $net.$j
        echo "" >&12
    } &
done
wait
rm -rf $pipfile

3.52.21. 修改SSHD配置文件,提升SSH安全性

#!/bin/bash
#功能描述(Description):修改SSHD配置文件,提升SSH安全性.

config_file="/etc/ssh/sshd_config"
PORT=12345

#将默认端口号修改为自定义端口号.
if grep -q "^Port" $config_file;then
    sed -i "/^Port/c Port $PORT" $config_file
else
    echo "Port $PORT" >> $config_file
fi

#禁止root远程登陆SSH服务器.
if grep -q "^PermitRootLogin" $config_file;then
    sed -i '/^PermitRootLogin/s/yes/no/' $config_file
else
    sed -i '$a PermitRootLogin no' $config_file
fi

#禁止使用密码远程登陆SSH服务器.
if grep -q "^PasswordAuthentication" $config_file;then
    sed -i '/^PasswordAuthentication/s/yes/no/' $config_file
else
    sed -i '$a PasswordAuthentication no' $config_file
fi

#禁止X11图形转发功能.
if grep -q "^X11Forwarding" $config_file;then
    sed -i '/^X11Forwarding/s/yes/no/' $config_file
else
    sed -i '$a X11Forwarding no' $config_file
fi

#禁止DNS查询.
if grep -q "^UseDNS" $config_file;then
    sed -i '/^UseDNS/s/yes/no/' $config_file
else
    sed -i '$a UseDNS no' $config_file
fi