Learning Bash 学习bash

写了一周的shell脚本人都写吐了,看会learning bash总结下一些shell的特性和技巧.

关于 @ 的区别

首先shell里有个变量叫做IFS(Internal Field Seprator) 内部域分格符.
这东西的大致用处是这样的:

>> a="alice,in,wonderland"
>> IFS=,
>> for i in "$a"; do echo $i; done
alice in wonderland

如果我们需要打印输入的参数但是不是用空格分割的,而是通过逗号来区别的,那我们的脚本可能要这么写:

IFS=,
echo "$*"
>> ./shell.sh alice dormouse hatter
alice,dormouse,hatter

还有就是如果你有这样的一个函数

function countargs
{
echo "$# args."
}

当你调用countargs “"countargs"@”会有不同的结果
countargs “"1args,countargs"@”会打印3 args.
也就是说”$@”通常会以字符串中的双引号作为分割的标志.
更换IFS的值是很有风险的,但也可能并不会发生什么事如果我们并不滥用他,当然使用完直接重置就行了.



字符串变量操作

 ${varname:-world} :初始化变量  
 ${varname:=world} :同上,但是不能对坐标变量使用.比如$1 
 ${varname:=message} :如果$varname未定义则打印出messge并退出脚本运行  
 ${varname:+word} :如果$varname已定义就返回word.  
 ${varname:offset:length} :字符串切片  
 ${variable#pattern} :匹配字符串前端最短部分并删除
 ${variable##pattern} :匹配字符串前端最长部分并删除
 ${variable%pattern} :匹配字符串前端最短部分并删除
 ${variable%%pattern} :匹配字符串前端最短部分并删除
 ${variable/pattern/string}:匹配并替换字符串
 # 注意:只有*string这样的pattern才会有最短最长的匹配区别.

getopt:

ARGS=`getopt -a -o P:I: -l prefix:,type: -- "$@"`
[ $? -ne 0 ] &&usage
eval set -- "${ARGS}" 
while true
do
    case "$1" in
    -h|--help)
        usage
        exit 0
        ;;
    --)
        shift
        break
        ;;
        esac
shift
done

重定向输入

{
TERM=vt100
# assume this as a default
line=$(tty)
while read dev termtype; do
if [ $dev = $line ]; then
TERM=$termtype
echo "TERM set to $TERM."
break;
fi
done
} < /etc/terms

bash 命令调用流程

  1. 分割command为一个个的tokens 分割符有SPACE,TAB,NEWLINE.;,(,),<,>,|和&.
    token的类型有words,keywords,I/O 重定向和分号.
  2. 检查第一个token是否为引用或者反斜杠(“\”),,如果这个这个token是个关键字,比如if之类的控制结构(control-structure)关键字,function,{,或者(,那么这个指令其实是一个组合指令.读取下一个指令并重新开始进程.
    如果这个token不是组合指令,而是一个控制结构(control-structure)的”middle”,比如说是then,else,do或者fi,done这样的end标志,或者是逻辑操作符,shell会报出一个语法错误(syntax error)
  3. 检查指令的第一个word是否是个是个引用(alias),如果找到相应的引用,那就返回setp1重新开始.否则就进入到step4.这个方案允许递归别名.同样允许用于关键字的别名,比如alias aslongas=while
    or alias procedure=function.
  4. 执行大括号扩展,例如,a{b,c}会变成ab ac.
  5. 将~替换为用户的home目录($HOME),如果他是第一个单词的开头.将~user替换为改用户的home目录.
  6. 替换参数中,或者说变量中所有开头为$的表达式(expression
  7. 替换指令中所有$(string)格式的表达式
  8. 计算所有$((string))格式的算术表达式.
  9. 再次将替换所有参数,指令和算术表达式的指令分割成单词(words,这次使用的分隔符为$IFS而不是step1中的那些元字符.
  10. 对所有有*,?或者[/]的地方执行路径名扩展,a.k.a.通配符扩张.
    11.使用第一个单词作为指令,在下列这些地方寻找其来源(source):先是函数指令(function command),然后是内建(built-in)指令,之后会去寻找在$PATH路径中的文件.
  11. 在设置I/O重定向和其他一些操作之后执行指令.

虽然这里的操作很多,但这还不是完整的流程.

example:

#我们先
alias ll="ls -l"
# 然后执行
ll $(type -path cc) ~alice/.*$(($$%1000))

shell会怎么调用这个命令呢.

# step1:先进行一次分割
ll   $(type   -path  cc)  ~alice/   .*(($$%1000))  
# step2:ll不是一个关键字,step2跳过

# step3:将ll 替换为 ls -l,因为这是一个alias,然后重复step1将其分割为"ls"  "-l"
ls -l $(type -path cc) ~alice/.*$(($$%1000))

# step4:没有大括号,所以不会做任何事.

# step5:将~alice替换为/home/alice
ls -l $(type -path cc) /home/alice/.*$(($$%1000))

# step6:将$$之类的变量替换掉,这里假设是2537
ls -l $(type -path cc) /home/alice/.*$((2537%1000))

# step7: 执行指令type -path cc然后替换掉$(type -path cc)
ls -l  /usr/bin/cc /home/alice/.*$((2537%1000))

# step8: 计算算术式2537%1000并替换
ls -l  /usr/bin/cc /home/alice/.*537

# step9:再次使用IFS 分割,这里没有做任何事.

# step10:替换所有包含通配符的目录文件名,这里是.*537
# step11: 寻找ls指令,最终在/usr/bin目录下找到了.
/usr/bin/ls -l  /usr/bin/cc /home/alice/.*537
# step12:执行/usr/bin/ls 带着选项-l和两个参数.

除了上述之外,还有五个步骤会修改执行的指令:quoting;使用command,buildtin或者
enable指令或者使用高级指令eval.

个人感觉比较重要的是指令的调用顺序和IFS分割符的调用时机,还有就是有个大致替换的顺序,还有就是变量和words之间的区别.

插一下讲一下昨天写脚本时候遇到的问题,脚本大概是这样的

#!/bin/bash

IFS="
"
all=`cat deps`
cd require
for i in $all
do
rpms=$i
# 去除注释
if [ "${rpms%% *}" == "###" ];then
echo $rpms
else
yum -y install $rpms
[ $? -ne 0 ] && echo $rpms >> ./error
fi
done

就是安装一些依赖而已,但是执行的时候却会报错说有些包找不到,但是手动安装又是找的到的.
于是开debug

$ bash -x -e deps.sh
.......
+ yum -y install "net-tools   " 

原因就是没搞清楚字符串和我们平时输入的参数是有区别的.yum找不到

"net-tools     "

的这个包.解决也很简单,改成

yum -y install ${rpm// /}

把字符串中的空格去掉就行了.


sed 的一些用法

$ cat phone.txt
5555551212
5555551213
5555551214
6665551215
6665551216
7775551217

# 使用&来记录批评的内容
$ sed -e 's/^[[:digit:]][[:digit:]][[:digit:]]/(&)/g' phone.txt
(555)5551212
(555)5551213
(555)5551214
(666)5551215

(666)5551216
(777)5551217


# 多次使用脚本
$ sed -e 's/^[[:digit:]]\{3\}/(&)/g'  \ 
   -e 's/)[[:digit:]]\{3\}/&-/g' phone.txt 
(555)555-1212 
(555)555-1213 
(555)555-1214 
(666)555-1215 
(666)555-1216 
(777)555-1217


# 使用()和\num来标记使用匹配的块.
$ cat phone.txt | sed 's/\(.*)\)\(.*-\)\(.*$\)/Area \ 
   code: \1 Second: \2 Third: \3/' 
Area code: (555) Second: 555- Third: 1212 
Area code: (555) Second: 555- Third: 1213 
Area code: (555) Second: 555- Third: 1214 
Area code: (666) Second: 555- Third: 1215 
Area code: (666) Second: 555- Third: 1216 
Area code: (777) Second: 555- Third: 1217

build-in declare
设置变量属性
example

$ declare -i val3=12 val4=5
$ declare -i result2
$ result2=val3*val4

带点错误处理的脚本包装

在写脚本的时候觉得直接执行出了错误不能追踪,而且想写一个带点动画的安装脚本,于是就对执行的脚本命令进行了包装.


waiting() {
    local pid="$1"
    Loading &
    local anime_pid="$!"
    # 对用户主动crtl-Z做处理.
    trap "Cance $anime_pid $pid; echo -e \"\e[0m\e[?25h\"; exit 1; " 2
    # 等待脚本执行结果
    wait $pid
    status="$?"

    #恢复光标到最后保存的位置
    tput rc
    # 发送信号量给动画进程,如果出错座退出处理                          
    [ $status -eq 0 ] && kill -6 $anime_pid >/dev/null 1>&2
    [ $status -ne 0 ] && kill -7 $anime_pid >/dev/null 1>&2 && exit 1
    sleep 1
    # exit $status
}

spin=('[ \e[31m●\e[32m●\e[33m●\e[34m● \e[39m]' '[ \e[32m●\e[33m●\e[34m●\e[31m● \e[39m]' '[ \e[33m●\e[34m●\e[31m●\e[32m● \e[39m]' '[ \e[34m●\e[31m●\e[32m●\e[33m● \e[39m]')

#   输出动画
Loading() {
    # 捕获信号量,根据信号量判断执行结果
    trap 'echo -ne "[\e[92m Done \e[39m]\n";exit 0;' 6
    trap 'echo -ne "[\e[91mfailed\e[39m]\n";exit 1;' 7
    cnt=0
    while [ 1 ]
    do

        tput sc                         
        echo -ne  "${spin[$((cnt % 4))]}"
        sleep 0.5
        tput rc                         
        cnt=$((cnt+1))
    done 
}

print_line(){
    echo -en "\e[0m"
    string1="Begin $1"
    printf '%*.*s' 0 $((LEFT_SIDE)) "$blank"
    echo -en " \e[1m"
    printf '%s' "$string1"
    echo -en " \e[21m"
    printf '%*.*s' 0 $((padlength - ${#string1} - 8 )) "$pad"
}

wrap_cmd() {
    Description=$1;shift
    [ ! -z "$Description" ]&& print_line "$Description" 
    for script in "$@"
    do
    # 执行脚本的步骤 -x显示具体的执行步骤 -e 表示在其中有命令出错的时候退出执行
    # 输出log
    eval " bash  -x -e $script 1>> $LOGFILE 2>$ERR_LOG | tee -a $ALLLOG"
    st="$?"
    sleep 1
    [ $st -ne 0 ]  && exit 1
    done
    exit 0

}

使用的方法

#!/bin/bash 

source base.sh
wrap_cmd "What ever I Done"\
    "/tmp/main.sh"\
    &
echo "$pid"
waiting "$pid"

实现的效果是这样的
这里写图片描述
这里写图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值