shell脚本


协程,协程可以同时做两件事。它在后台生成一个子shell,并在这个子shell中执行命令。要进行协程处理,得使用coproc命令,还要有在子shell中执行的命令.当外部命令执行时,会创建一个子进程。这种操作被称为衍生(forking)。

break:中断整个循环

continue:将控制转移到下一段代码,但是会继续执行循环

exit:退出整个循环

return:用于在函数中将数据返回,返回一个结果或代码给调用脚本

shell脚本中由于安全原因不支持执行suid,sgid,sticky bit
命令行参数$1,$2,$3...$9是位置参数,$0指向实际的命令、程序、shell脚本或函数

在一个函数中出现的$0,$2等由函数本身使用
$,$@不加双引号指定了所有的命令行参数,加上双引号,$将整个参数列表作为一个参数来获取,$@获取整个参数列表,并将其分割成不同的参数

当shell脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(加载顺序通常是/etc/profile、~/bash_profile、~/bash_rc、/etc/bashrc)
常见的全局变量配置文件:
/etc/profile
/etc/bashrc
/etc/profile.d/
诺要在登陆后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/下即可(无需加执行权限)

```设置登录提示的两种方式:
1.在/etc/motd里增加提示的字符串
2.在/etc/profile.d/下面增加脚本

> \n:换行
\r:回车
\t:制表符

> chmod 4755 设置某个程序总是做为所有者执行(suid)

> chmod 2755 设置某个程序总是做为文件所有者所属的组成员执行(sgid)

> chmod 6755 设置某个程序总是做为文件所有者和文件所有者的组成员执行

> echo -n :不换行输出

> 诺某个程序在正常退出前被终止,通常情况下可以捕获到一个退出信号,该退出信号成为一个陷阱(trap),退出信号:

  数字  | 信号          | 含义          |
:--:|:---------:|:---------:|
0   |   -       |   正常终止,脚本结束|
1   |   SIGHUP  |   挂起,线路断开 
2   |   SIGINT  |   终端中断,通常是Ctrl+C 
3   |   SIGQUIT |   退出键,子进程在终止前死掉
9   |   SIGKILL |   kill -9 命令,不能捕获这种推出状态
15  |   SIGTERM |    kill命令的默认操作
19  |   SIGSTOP |     停止通常为Ctrl+Z
> **trap命令可以捕获信号**

> ps -ef :除内核进程外所有进程的完整列表

> ps -aux :根据CPU的使用时间%CPU排序后显示的进程列表

> /etc/motd/文件在每次用户登录时显示

> tr :大小写形式转换   tr  '[a-z]' '[A-Z]' 或者 tr       '[A-Z]' '[a-z]' 注意:需要使用单引号将[]扩起来

> typeset -u 变量名 :用于大写,设置了变量的属性之后,每次为变量赋值文本字符串时,都自动转换为大写字符

> typeset -l 变量名:用于小写,设置了变量的属性之后,每次为变量赋值文本字符串时,都自动转换为小写字符

> cron 表是一个系统文件,系统每分钟读取一次,而且将执行安排在该时间段执行的所有条目.

> crontab -e 创建一个cron表,crontab -l 列出当前用户的cron表内容

> at 根据时间来执行命令,比如十分钟后执行 at now + 10 minutes

> 2>&1 /dev/null :将文件描述符2指定的标准错误(stderr)重定向到文件描述符1指定的标准输出(stdout)

> if [  -s file ]:文件大小非零时为真
***

#!/bin/bash
out_file="/root/out_file"
in_file="/etc/passwd"

${out_file}
exec 4<&1 #4文件描述符从标准输出读入数据
exec 1>${out_file} #标准输出重定向到${out_file}
while read LINE #read LINE 读取文件中的每一行
do
echo "$LINE"
:
done < ${in_file} #while循环从${in_file}读入数据
exec 1<&4 #将重定向到${out_file}的标准输出重新指向STDOUT
exec 4>&- #关闭文件描述符4


linux系统将每个对象当作文件处理。这包括输入和输出进程。linux用文件描述符(file descriptor)来标示每个文件对象。文件描述符是一个非负整数,可以唯一标示会话中打开的文件。每个进程一次最多可以有9个文件描述符。
文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误
#!/bin/bash
out_file="/root/out_file"
in_file="/root/in_file"
exec 3>&1
exec 1>${out_file}
while read line 
do 
echo "$line"
done < ${in_file}
exec 1>&3
exec 3>&-

这个例子首先,脚本将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。这意味着任何发送给文件描述符3的输出都将出现在显示器上。第二个exec命令将STDOUT重定向到文件,shell现在会将发送给STDOUT的输出直接重定向到输出文件中。但是,文件描述符3仍然指向STDOUT原来的位置,也就是显示器。如果此时将输出数据发送给文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向了。
在向STDOUT现在指向一个文件发送一些输出之后,脚本将STDOUT重定向到文件描述符3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又指向了它原来的位置:显示器。


逐行处理文件2

#!/bin/bash 
out_file="/root/out_file"
in_file="/etc/profile"
>${out_file}
exec 4>&1
exec 1>${out_file}
for line in `cat ${in_file}`
do
    echo "${line}"
done
exec 1>&4
exec 4>&-

逐行处理文件3

#!/bin/bash
while read line 
do
    echo "$line"
done < <(ps)

done后面的<与<之间必须有空格,执行脚本使用bash执行sh执行脚本会报错


打印色彩进度条

#!/bin/bash
num=0
str='#'
max=100
pro=('|' '/' '-' '\')
while [ $num -le $max ]
do
((color=30+num%8))
echo -en "\e[1;"$color"m"
let index=num%4
printf "[%-100s %d%% %c]\r" "$str" "$num" "${pro[$index]}"
let num++
sleep 0.1
str+='#'
done
printf "\n"
echo -e "\e[1;30;m"

expect实现免交互

#!/bin/bash 
function expect_mianmi() {
/usr/bin/expect <<EOF
spawn ssh ${i} "需要执行的命令"
expect  {
"yes/no"    {exp_send "yes\r";exp_continue}
"*password" {exp_send "需要登陆的主机密码\r"}
}
expect eof
EOF
}
for i in `cat ip_list`          #ip_list是需要登录的主机列表
do
expect_mianmi
done

捕获命令输出

#!/bin/bash
out_file="/tmp/outfile.out"
cat /dev/null >> ${out_file}
until  [ -s ${out_file}]
then
echo "test" >> ${out_file}
fi
more ${out_file}

生成随机数

#!/bin/bash
ran_dom=$$      #脚本执行后产生的PID
upper_limit=$1  #脚本传递的第一个参数
random_number=$((${ran_dom} % ${upper_limit} + 1))  #用PID除以传递的参数求模+1
echo "$$"
echo "${random_number}"

这个脚本只能生成4位数的随机数

高亮显示文本

cat /etc/hosts  | sed s#'km'#$(tput smso)'km'$(tput rmso)#g

删除文件中的重复的行

如果有一个有重复行的文件my_list,想要将my_list删除重复行保存到my_list_no_repeats,可以使用如下命令:
uniq  mylist  my_list_no_repeats
只查看一次重复的行可以使用:
cat my_list | uniq 

删除文本中的空行

cat my_file | sed '/^$/d'
sed '/^$/d' my_file

测试空变量

#!/bin/bash
VAL=        #设置一个空变量
if [[ -z "$VAL" && "$VAL" = '' ]]   
then
    echo "VAL is null"
fi
VAL=25      #设置一个有值的变量
if [[ ! -z "$VAL"  && "$VAL" != '' ]]
then
    echo "VAL is not NULL"
fi
#双中括号进行测试,unix脚本中说变量必须使用双引号扩起来,实际双中括号更多的用来进行字符串比较,使用"[[]]"可以在判断中使用&&、||而不是-a,-o
#"["和test是bash的内嵌命令,而"[[]]"是关键字

简单发送邮件告警

#!/bin/bash
mail_file="/tmp/mailfile.out"       #定义邮件的内容
mail_list="[email protected]"        #收件人地址
> ${mail_file}                      #清空邮件内容
function check_system {
if [ -s ${mail_file} ]              #如果邮件内容不为空则执行
then
    mail -s "Filesystem Full" ${mail_list} > ${mail_file}   #发送邮件给收件人,filesystem full是邮件主题
fi
file_sys="/"            #测试,定义邮件的内容
file_size="98%"
this_host=`uname -n`
echo "${this_host} ${file_sys} is use ${file_size}" | tee -a ${mail_file}   #tee命令将echo同时输出到显示器和指定的文件中
}
check_system

“.”作为进度条显示进度

#!/bin/bash
while true 
do
    echo -e ".\c"       #在同一行打印"."不换行
    sleep 1             #很重要,不指定时间循环将输出很多"."
done &                  #让循环在后台运行,会有运行pid
bg_pid=$!               #"#!表示上一个后台进程的pid"用变量bg_pid来保存循环的运行pid
source /root/shell/while_read_line.sh   
kill "$!"               #在实际脚本执行完毕后杀死进度条循环

旋转线进度条

#!/bin/bash
rcount_num=0            #设置一个递增的变量
while :
do
    ((rcount_num = rcount_num + 1)) #让变量的值递增
    case ${rcount_num} in 
        "1")
            echo -e "-\b\c"         #"\b"退回光标
            sleep 1                 #"\c"继续在统一行显示
        ;;
        "2")
            echo -e '\'"\b\c"       #"\"是特殊字符需要转义
            sleep 1
        ;;
        "3"
            echo -e "|\b\c"
            sleep 1
        ;;
        "4")
            echo -e "/\b\c"
            sleep 1
        ;;
        *)
            rcount_num=0            #还原变量的值,让循环继续
    esac
done &                              #循环后台运行产生pid         
bg_pid=$!                           #"#!"保存上一个后台进程的pid
sh /root/shell/while_read_line.sh
kill $!

date:20190304

env或者set显示默认的环境变量;
unset消除本地变量和环境变量;
按天打包网站的站点目录程序 CMD=$(date +%F);
按时间打包/etc/目录:tar czf $(date +%F).tar.gz /etc  ; 
位置变量 作用说明
$0 获取当前执行脚本的路径名,如果执行脚本包含了路径,那么就包括脚本路径
$n 获取当前执行shell脚本的第n个参数,n=1..9,当n为0时,表示脚本的文件名;如果n>9,则用大括号括起来,例如${10},接的参数以空格隔开
$* 获取当前shell脚本所有传参的参数,不加引号和$@相同;如果给$加上双引号,例如"$\",则表示将所有的参数视为单个字符串,相当于"$1 $2 $3"
$@ 获取当前shell脚本所有传参的参数,不加引号和$*相同;如果给$@加上双引号,例如:"$@",则表示将所有的参数视为不同的独立字符串,相当于"$1" "$2" "$3" "$..."。
$? 获取上一个指令执行状态的返回值,
$$ 获取当前执行的shell脚本的进程号(pid)
$! 获取上一个在后台工作进程的进程号
$_ 获取在此之前执行的命令或脚本的最后一个参数

date:20190305

#位置参数大于9需要用大括号括起来
cat n.sh
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}
sh n.sh {a..z}
echo $0 ,如果不带脚本路径,输出的就是脚本的名称,带上路径执行脚本,$0也会带着路径
set -- "I am" km boy     #通过set设置3个字符串参数,"--"表示清除所有的参数变量,重新设置后面的参数变量

实现系统中多次执行某一个脚本,同一时间运行的只有一个。

#!/bin/bash
pid_path=/tmp/a.pid
if  [ -f ${pid_path} ]
then
    kill -9 `cat ${pid_path}`
    rm -f ${pid_path}
fi
echo $$ > ${pid_path}
sleep 300 &

date:20190307

exec 命令能够在不创建新的子进程的前提下,转去执行指定的命令,当指定的命令执行完毕后,该进程(也就是最初的shell)就终止了。
当使用exec打开文件后,read 命令每次都会将文件指针移动到文件的下一行进行读取,直到文件末尾,利用这个可以实现处理文件功能。
seq 10 > /tmp/tmp.log

#!/bin/bash
exec < /tmp/tmp.log
while read line 
do 
    echo $line
done 
echo ok
shift:shift命令主要是将$1 $2等位置参数进行左移

#!/bin/bash
echo $1 $2 
if [ $# -eq 2 ]
then
    shift
fi
echo $1

变量子串说明

id 表达式 说明
1 ${parameter} 返回变量$parameter的内容
2 ${#parameter} 返回变量$parameter内容的长度(按字符),也适用于特殊变量
3 ${parameter:offset} 在变量${parameter}中,从位置offset之后开始提取子串到结尾
4 ${parameter:offset:length} 在变量${parameter}中,从位置offset之后开始提取长度为length的字串
5 ${parameter#word} 从变量${parameter}开头开始删除最短匹配的word子串
6 ${parameter##word} 从变量${parameter}开头开始删除最长匹配的word子串
7 ${parameter%word} 从变量${parameter}结尾开始删除最短匹配的word子串
8 ${parameter%%word} 从变量${parameter}结尾开始删除最长匹配的word子串
9 ${parameter/pattern/string} 使用string代替第一个匹配的pattern
10 ${parameter//pattern/string} 使用string代替所有匹配的pattern
km_laoge='I am huge'
echo ${#km_laoge}       #返回变量值的长度
echo ${km_laoge:2}      #截取km_laoge变量的内容,从第2个字符之后开始截取,默认截取后面字符的全部,第二个字符不包含在内。
echo ${km_laoge:2:2}    #截取km_laoge变量的内容,从第2个字符之后开始截取,截取2个字符
oldboy="I'm oldboy,oldboy"
echo ${oldboy/oldboy/oldgirl}       #"/"表示替换匹配的第一个字符串
echo ${oldboy//oldboy/oldgirl}      #"//"表示替换匹配的所有字符串
touch stu_1029999_{1..5}_finished.jpg
for i in `ls *finished.jgp`; do mv $i  `echo {$i/_finished/}` ; done 
表达式 说明
${parameter:-word} 如果parameter的变量的值为空或未赋值,则会返回word字符串并替代变量的值
${parameter:=word} 如果parameter的变量的值为空或未赋值,则设置这个变量值为word,并返回其值。位置变量和特殊变量不适用
${parameter:?word} 如果parameter的变量的值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值
${parameter:+word} 如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将代替变量的值

变量计算

a=10
echo ${a++}   #如果a在运算符(++或者--)的前面,那么在输出整个表达式时,会输出a的值
echo ${++a}   #如果a在运算符(++或者--)的后面,那么在输出整个表达式时,先进行自增或自减计算
i=1
expr $i + 6 > /dev/null 2>&1
echo $?       #利用expr计算来判断变量是否为整数
echo `seq -s '+' 10`=`seq -s '+' 10|bc`
declare -i A=30 B=8    #<==declare -i参数可以将变量定义为整形
A=A+B                  #<==因为已声明是整型,因此可以直接进行运算

shell脚本的条件测试与比较

  • test命令和[]是等价的,[[]]为扩展的test命令,()常用于计算
  • 在[[]]中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方
  • && 、||、>、<等操作符可以应用于[[]]中,但不能应用于[]中,在[]中一般用-a、-o、-gt、-lt代替上述操作符
-z 如果测试字符串的长度为0,则表达式成立
-d 文件存在且为目录为真
-f 文件存在且为普通文件为真
-e 文件存在则为真
-r 文件存在且可读则为真
-s 文件存在且文件大小不为0则为真
-w 文件存在且文件可写则为真
-x 文件存在且可执行则为真
-L 文件存在且为链接文件则为真
f1 -nt f2 文件f1比文件f2新则为真
f1 -ot f2 文件f1比文件f2旧则为真
-n "字符串" 若字符串的长度不为0,则为真
-z "字符串" 若字符串的长度为0,则为真
"串1" = "串2" 若字符串1等于字符串2,则为真,可使用"=="代替"="
"串1" != "串2" 若字符串1不等于字符串2,则为真,但不能用"!=="代替"!="
  • 比较符(例如=和!=)的两端一定要有空格
  • "!="和"="可用于比较两个字符串是否相同

wget --spider --timeout=10 --tries=2 www.baidu.com 模拟爬取