综合脚本练习

  1. 写一个脚本, 接收一个文件名作为参数, 用 Here Document 输出信息让用户选择将文件压缩为 gzip , bzip2 , zip .
#!/usr/bin/bash

menu (){
clear
cat << EOF
Switch file compress format:
g) gzip
b) bzip2
z) zip
EOF
}

if [ ! -f $1 ]; then
    echo "Usage: $0 <filename>"
    exit
fi

menu

while true
do
    read -n1 compress
    case $compress in
    g)
        gzip -k $1
        ;;
    b)
        bzip2 -k $1
        ;;
    z)
        zip $1 $1
        ;;
    *)
        menu
        ;;
    esac
done
  1. 实现可交互的目录备份脚本:
#!/usr/bin/bash

if [ $# -ne 0 ]; then
    echo "Usage: $0"
fi

BACKUPDIR=~/Pictures
if [ ! -e $BACKUPDIR ]; then
    echo "ERROR: no backup dir $BACKUPDIR"
    exit
fi

TARGETDIR=/tmp/backups
if [ ! -d $TARGETDIR ]; then
    echo "mkdir..."
    mkdir $TARGETDIR
fi

cat << EOF
Switch compress type:
r) incremental backup
c) full backup
EOF

read -n1 cptype

case $cptype in
r)
    OPTION="-rf"
    ;;
c)
    OPTION="-cf"
    ;;
*)
    echo "ERROR"
    exit
    ;;
esac

echo "Taring the $BACKUPDIR"
echo "This may take several minutes"
FILENAME=$TARGETDIR/ `date +%y%m%d` .tar
tar $OPTION $FILENAME $BACKUPDIR &> /dev/null
ls -sh $FILENAME | awk '{ print $1 " data has been backed up at " $2}'
  1. 写一个脚本, 完成与 cp -r /etc ~/etcbackup 相同的功能.
  2. 写一个脚本, 显示一个路径下的最大的 N 个文件以及最近修改的 N 个文件.
#!/usr/bin/bash

if [[ $# != 1 || ! -d "$1" ]]; then
    echo "Usage: $0 <dir-name>"
    exit
fi

PS3="Make your choice (q for exit): "
select MODE in "top 5 sort by size", "top 5 sort by recent";
do
    case $REPLY in
    1)
        ls -Sl | awk 'NR<7 && NR>1 {print $9, $5}'
        ;;
    2)
        ls -tl | awk 'NR<7 && NR>1 {printf "%s %s-%s-%s\n", $9, $6, $7, $8}'
        ;;
    'q')
        exit
        ;;
    esac
done
  1. 解释为什么最好在变量上加双引号.

01. 防止强转字符串报错.
02. 防止变量中的空格被错误分割, 如: 

> FILENAME="iop poi"
> ls $FILENAME  # 被当做两个文件
ls: cannot access 'iop': No such file or directory
ls: cannot access 'poi': No such file or directory
> ls "$FILENAME"  # 被当做一个文件
ls: cannot access 'iop poi': No such file or directory
  1. 写一个文本模式浏览器, 用数组保留最近十条可回退的历史页面.
#!/usr/bin/bash

declare -a histarr
p=1

while true; do
    read -p "Enter a URL (b for back; q for quit): " ACTION
    case $ACTION in
    b)
        if [ $((p-=2)) -lt 1 ]; then
            p=1
            echo "no history"
        else
            links -dump "/tmp/$p.html"
            echo ${histarr[$((p++))]}
        fi
        ;;
    q)
        exit
        ;;
    *)
        if `wget "$ACTION" -O "/tmp/$p.html" &> /dev/null` ; then
            histarr[$p]="$ACTION"
            links -dump "/tmp/$((p++)).html"
        else
            echo "no such website"
        fi
        ;;
    esac
done
  1. 在脚本中完成下列功能:
#!/usr/bin/bash

# 显示当前被执行的脚本的名字
echo $0

# 显示被传进来的第一个, 第三个和第十个参数
# 测试: bash arguments.sh `seq 11` 
echo $1 $3 ${10}

# 显示所有被传进来的参数
echo $@

# 打印参数总数
echo $#

# 如果传入的参数量大于 3, 用 shift 扔掉前 3 个
# 注意 $0 不会被偏移, 但会被计入 $#
if [ $# -ge 4 ]; then
    shift 3
fi

# 打印剩余的参数
echo $@
  1. 批量并发 ping 主机, 结果打印到屏幕并存储到文件.
#!/usr/bin/bash

>ip.txt  # 清空文件

for i in {2..254}; do
    {
        ip="127.0.0.$i"
        ping -c1 -W.5 $ip &> /dev/null
        if [ $? -eq 0 ]; then
            echo $ip | tee -a ip.txt
        fi
    }&
done

wait
echo "finish"
  1. 使用命名管道实现对并发进程数量的控制:
#!/usr/bin/bash

DATAPATTERN="^[1-9][0-9]*$"
if [[ ! $1 =~ $DATAPATTERN ]]; then
    echo "Usage: $0 <Positive Integer>"
    exit
fi

pipe=/tmp/$.fifo
if [ ! -p $pipe ]; then
    mkfifo "$pipe"
fi

# 通过文件描述符操作 fifo 不会堵塞
exec 8<> "$pipe"
rm "$pipe"

seq $1 >&8  # 初始化生产固定数量

# 其实是一个生产者消费者模型
# 能够保证同时执行的进程数是可控的
infoping () {s
    ip="127.0.0.$1"
    ping -c1 -W1 $ip &> /dev/null
    if [ $? -eq 0 ]; then
        echo "$ip is up"
    else
        echo "$ip is down"
    fi
    sleep 1
    echo >&8  # 执行完毕就生产一个
}

for i in {2..15}
do
    read -u 8  # 消费, 没有就等待
    infoping $i &  # 消费一个执行一个
done

wait
echo "finish"
  1. 查看系统中当前所有连接的种类和数量.
#!/usr/bin/bash

declare -A status

LINK= `ss -an | awk '{ print $1 }'` 
for line in $LINK; do
    let status[$line]++
#     echo $line
done

for key in ${!status[@]}; do
    echo "$key: ${status[$key]}"
done

# ss -an | awk '{ s[$1]++ } END { for (i in s) { print i, s[i]} }'
  1. 写一个求阶乘的函数, 要求传参必须使用数组.
#!/usr/bin/bash

function ARRAYPOD () {
    ret=1
    for i in $@; do
        let ret*=$i
    done
    echo $ret  # 用等号接收
    return $ret  # 保存在 $? 中
}

ARRAY=(1 2 3 4 5)
result= `ARRAYPOD ${ARRAY[*]}` 
echo $result $?
  1. 通过 trap 实现程序退出确认.
ex_confirm () {
  read -n1 -p "Do you want to quit [Y/N]?" JUDGE
  echo
  case $JUDGE in
  y | Y)
    exit
    ;;
  esac
}

trap ex_confirm INT

while :
do
  sleep 1
done
  1. 实现日志存储, 要求按以下格式存储 log/模块名/当前月的几号/当天的第几小时.log :
#!/usr/bin/bash

if [ $# -ne 2 ]; then
    echo "Usage: $0 <module name> <exec file string>"
    echo "e.g.: $0 LUNATIC \"python3 niconicov4.py --model=[8,16,32]\""
    exit
fi

FIFONAME="/tmp/$1.fifo" 

if [ ! -p $FIFONAME ]; then
    mkfifo $FIFONAME
fi

exec 8<> "$FIFONAME"

gen_filename () {
    while :
    do
        TIMEARR=( `date "+%H %M %S"` )
        LOGPATHNAME="log/$1/${TIMEARR[0]}"
        if [ ! -d "$LOGPATHNAME" ]; then
            mkdir -p "$LOGPATHNAME"
        fi
        LOGFILENAME="$LOGPATHNAME/${TIMEARR[1]}.log"
        cat <&8 > "$LOGFILENAME" &
        sleep $[60-${TIMEARR[2]}]
    done
}

$2 2>&8 & MAINPID=$!
gen_filename $1 & CPID=$!

trap "kill $MAINPID $CPID; rm "$FIFONAME"" EXIT
wait
  1. 打印并重定向到文件
#!/usr/bin/bash

DATETIME= `date` 
echo $DATETIME |tee myex22.log
# date |tee myex22.log
USERS= `who` 
echo $USERS |tee -a myex22.log
RUNNING= `uptime` 
echo $RUNNING |tee -a myex22.log
  1. 重定向脚本中的部分信息:
#!/usr/bin/bash

SUCCESS=0
E_NOARGS=56

if [ -z "$1" ]; then
    echo "Usage: `basename $0` <package-name>"
    exit $E_NOARGS
fi
apt-cache pkgnames | egrep '^nginx
$mdFormatter$89$mdFormatter$

!/usr/bin/bash

[ $# -eq 0 ] && directorys= pwd || directorys=$@

linkchk () {

for element in $1/*; do
    [ -h $element -a ! -e $element ] && echo "$element"
    [ -d $element ] && linkchk $element
done

}

for dir in $directorys; do

if [ -d $dir ]
then linkchk $dir
else echo "Usage: `basename $0` dir1 dir2 ..."
fi

done

exit 0


37. 创建一个内存文件系统.

!/usr/bin/bash

ROOT_UID=0 E_WRONG_USER=70

if [ "$UID" -ne "$ROOT_UID" ]; then

echo "Must be root to run \" `basename $0` \"."
exit $E_WRONG_USER

fi

MOUNTPT=/mnt/ramdisk DEVICE=/dev/ram0 SIZE=2000 BLOCKSIZE=1024

if [ ! -d "$MOUNTPT" ]; then

mkdir "$MOUNTPT"

fi

dd if=/dev/zero of=$DEVICE bs=$BLOCKSIZE count=$SIZE echo "Creating ramdisk $DEVICE"

mke2fs $DEVICE mount $DEVICE $MOUNTPT chmod 777 $MOUNTPT echo "$MOUNTPT is available now."

exit 0


# 综合练习

## 实现复杂的自动补全

下面再结合前面三个补全命令(complete/compgen/compopt)和内置变量,写了例子说明下。

~~~shell
# cat /etc/bash_completion.d/foo.bash   
_foo()  
{  
    COMPREPLY=()  
    local cur=${COMP_WORDS[COMP_CWORD]};  
    local cmd=${COMP_WORDS[COMP_CWORD-1]};  
    case $cmd in  
    'foo')  
          COMPREPLY=( $(compgen -W 'help test read' -- $cur) ) ;;  
    'test')  
          local pro=( $(awk '{print $1}' /data/a.txt) )  
          COMPREPLY=( $(compgen -W '${pro[@]}' -- $cur) ) ;;  
    '*')  
          ;;  
    esac  
    if [[ "${COMP_WORDS[1]}" == "read" && ${COMP_CWORD} -eq 2 ]]; then  
          local pro=($(pwd))  
          cd /data  
          compopt -o nospace  
          COMPREPLY=($(compgen -d -f -- $cur))  
          cd $pro  
    fi  
    return 0  
}  
complete -F _foo foo
~~~

例子中, foo有3个参数,分别是 help, read, test

read 测试遍历 /data 目录下所有文件

test 测试从文件中提取2级参数  
help 只是演示,没有特殊作用

现在跑下这个例子:

~~~shell
# mkdir /data  
# touch /data/a.txt  
# touch /data/b.txt  
# tree /data  
/data  
├── a.txt  
└── b.txt  

0 directories, 2 files  
# source /etc/bash_completion.d/foo.bash   
# foo [Tab][Tab]  
help  read  test    
# echo world1 >> /data/a.txt  
# echo world2 >> /data/a.txt  
# foo test world[Tab][Tab]  
world1  world2    
# foo read[Tab][Tab]  
a.txt  b.txt
~~~

## 历史记录权限控制

**实例**  
设置uid大于等于500的用户的history安全性  
需求:  
记录统一转移到/var/history目录下;  
用户无法删除自己的history文件,无法清空history;  
多个终端共享history,实时追加;  
限制history文件大小和保存的条数;  
举例用户,lionel;uid=522

~~~
1)配置全局环境变量文件/etc/profile
# vi /etc/profile       //添加以下内容
# add by coolnull
if [ $UID -ge 500 ]; then
    readonly HISTFILE=/var/history/$USER-$UID.log
    readonly HISTFILESIZE=50000
    readonly HISTSIZE=10000
    readonly HISTTIMEFORMAT='%F %T '
    readonly HISTCONTROL=ignoredups
    shopt -s histappend
    readonly PROMPT_COMMAND="history -a"
fi

创建目录结构
# mkdir /var/history

配置目录权限,使得用户有权限创建自己的history文件
# chmod 777 /var/history
# chmod a+t /var/history

2)限制用户删除自己的history文件
# chattr +a /var/history/lionel-522.log

3)限制用户修改自己主目录的环境变量配置文件
# chattr +a /home/lionel/.bash*

# lsattr /home/lionel/.bash*
-----a------- /home/lionel/.bash_logout
-----a------- /home/lionel/.bash_profile
-----a------- /home/lionel/.bashrc

4)禁止普通用户切换到系统中其他shell环境(一般包括csh, tcsh, ksh)
# chmod 750 tcsh(csh是tcsh的软连接,设置tcsh就可以了)
# chmod 750 /bin/ksh

普通帐号测试
[zhangfei@node1 ~]$ tcsh
-bash: /bin/tcsh: Permission denied
[zhangfei@node1 ~]$ ksh
-bash: /bin/ksh: Permission denied
~~~

 > /dev/null
if [ $? -ne $SUCCESS ]; then
    echo "Can not find $1 in apt cache list"
    exit 1
fi

# 只重定向大括号里的部分
{
    echo
    echo "Show packages details:"
    apt show "$1"
    echo
    echo "List packages based on $1:"
    apt-cache policy "$1"
} > "$1.details"

echo "Results of apt test in file $1.details"
exit 0
  1. 递归查找当前目录下的所有指向文件被删除的符号链接.
#!/usr/bin/bash

[ $# -eq 0 ] && directorys= `pwd` || directorys=$@

linkchk () {

    for element in $1/*; do
        [ -h $element -a ! -e $element ] && echo "$element"
        [ -d $element ] && linkchk $element
    done

}

for dir in $directorys; do

    if [ -d $dir ]
    then linkchk $dir
    else echo "Usage: `basename $0` dir1 dir2 ..."
    fi

done

exit 0
  1. 创建一个内存文件系统.
#!/usr/bin/bash

ROOT_UID=0
E_WRONG_USER=70

if [ "$UID" -ne "$ROOT_UID" ]; then

    echo "Must be root to run \" `basename $0` \"."
    exit $E_WRONG_USER

fi

MOUNTPT=/mnt/ramdisk
DEVICE=/dev/ram0
SIZE=2000
BLOCKSIZE=1024

if [ ! -d "$MOUNTPT" ]; then

    mkdir "$MOUNTPT"

fi

dd if=/dev/zero of=$DEVICE bs=$BLOCKSIZE count=$SIZE
echo "Creating ramdisk $DEVICE"

mke2fs $DEVICE
mount $DEVICE $MOUNTPT
chmod 777 $MOUNTPT
echo  "$MOUNTPT is available now."

exit 0

综合练习

实现复杂的自动补全

下面再结合前面三个补全命令(complete/compgen/compopt)和内置变量,写了例子说明下。


# cat /etc/bash_completion.d/foo.bash   

_foo()  
{  

    COMPREPLY=()  
    local cur=${COMP_WORDS[COMP_CWORD]};  
    local cmd=${COMP_WORDS[COMP_CWORD-1]};  
    case $cmd in  
    'foo')  
          COMPREPLY=( $(compgen -W 'help test read' -- $cur) ) ;;  
    'test')  
          local pro=( $(awk '{print $1}' /data/a.txt) )  
          COMPREPLY=( $(compgen -W '${pro[@]}' -- $cur) ) ;;  
    '*')  
          ;;  
    esac  
    if [[ "${COMP_WORDS[1]}" == "read" && ${COMP_CWORD} -eq 2 ]]; then  
          local pro=($(pwd))  
          cd /data  
          compopt -o nospace  
          COMPREPLY=($(compgen -d -f -- $cur))  
          cd $pro  
    fi  
    return 0  

}  
complete -F _foo foo

例子中, foo有3个参数,分别是 help, read, test

read 测试遍历 /data 目录下所有文件

test 测试从文件中提取2级参数
help 只是演示,没有特殊作用

现在跑下这个例子:


# mkdir /data  

# touch /data/a.txt  

# touch /data/b.txt  

# tree /data  
/data  
├── a.txt  
└── b.txt  

0 directories, 2 files  

# source /etc/bash_completion.d/foo.bash   

# foo [Tab][Tab]  
help  read  test    

# echo world1 >> /data/a.txt  

# echo world2 >> /data/a.txt  

# foo test world[Tab][Tab]  

world1  world2    

# foo read[Tab][Tab]  

a.txt  b.txt

历史记录权限控制

实例
设置uid大于等于500的用户的history安全性
需求:
记录统一转移到/var/history目录下;
用户无法删除自己的history文件,无法清空history;
多个终端共享history,实时追加;
限制history文件大小和保存的条数;
举例用户,lionel;uid=522

1)配置全局环境变量文件/etc/profile

# vi /etc/profile       //添加以下内容

# add by coolnull
if [ $UID -ge 500 ]; then

    readonly HISTFILE=/var/history/$USER-$UID.log
    readonly HISTFILESIZE=50000
    readonly HISTSIZE=10000
    readonly HISTTIMEFORMAT='%F %T '
    readonly HISTCONTROL=ignoredups
    shopt -s histappend
    readonly PROMPT_COMMAND="history -a"

fi

创建目录结构

# mkdir /var/history

配置目录权限,使得用户有权限创建自己的history文件

# chmod 777 /var/history

# chmod a+t /var/history

2)限制用户删除自己的history文件

# chattr +a /var/history/lionel-522.log

3)限制用户修改自己主目录的环境变量配置文件

# chattr +a /home/lionel/.bash*

# lsattr /home/lionel/.bash*
-----a------- /home/lionel/.bash_logout
-----a------- /home/lionel/.bash_profile
-----a------- /home/lionel/.bashrc

4)禁止普通用户切换到系统中其他shell环境(一般包括csh, tcsh, ksh)

# chmod 750 tcsh(csh是tcsh的软连接,设置tcsh就可以了)

# chmod 750 /bin/ksh

普通帐号测试
[zhangfei@node1 ~]$ tcsh
-bash: /bin/tcsh: Permission denied
[zhangfei@node1 ~]$ ksh
-bash: /bin/ksh: Permission denied

results matching ""

    No results matching ""