综合脚本练习
- 写一个脚本, 接收一个文件名作为参数, 用
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
- 实现可交互的目录备份脚本:
#!/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}'
- 写一个脚本, 完成与
cp -r /etc ~/etcbackup
相同的功能. - 写一个脚本, 显示一个路径下的最大的
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
- 解释为什么最好在变量上加双引号.
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
- 写一个文本模式浏览器, 用数组保留最近十条可回退的历史页面.
#!/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
- 在脚本中完成下列功能:
#!/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 $@
- 批量并发
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"
- 使用命名管道实现对并发进程数量的控制:
#!/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"
- 查看系统中当前所有连接的种类和数量.
#!/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]} }'
- 写一个求阶乘的函数, 要求传参必须使用数组.
#!/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 $?
- 通过
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
- 实现日志存储, 要求按以下格式存储
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
- 打印并重定向到文件
#!/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
- 重定向脚本中的部分信息:
#!/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
- 递归查找当前目录下的所有指向文件被删除的符号链接.
#!/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
- 创建一个内存文件系统.
#!/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