这是简洁的 Bash Programming 技巧系列的第三篇文章,这一系列的文章专门介绍 Bash 编程中一些简洁的技巧,帮助大家提高平时 Bash 编程的效率。有兴趣的同学可以回顾下之前的两篇文章(一)和续篇。

1. 替换语法${parameter/pattern/string}的妙用

${parameter/pattern/string}将parameter中匹配pattern的部分替换成string,例如下面的例子将字符串中的e替换成x:

$ str="three"
$ echo "${str/e/x}"   # thrxe

如果pattern部分以/开头,表示替换parameter中所有匹配的内容,例如:

$ str="three"
$ echo "${str//e/x}"  # thrxx

如果pattern部分以#开头,表示仅当parameter开始处匹配pattern的时候替换,例如:

str="three"
$ echo "${str/#e/x}" # three
$ echo "${str/#t/x}" # xhree

与此对应地是,如果pattern部分以%开头,表示仅当parameter结尾处匹配pattern的时候替换,例如:

$ str="three"
$ echo "${str/%e/x}" # threx

如果string部分为空,匹配pattern的部分被删除(替换为空),例如:

$ str="three"
$ echo "${str/h/}"  # tree

这个时候第二个斜杠可以删除,即:echo "${str/h}" 如果parameter是一个数组会怎么样呢?有兴趣的可以看看Bash的man手册说明:

man -P 'less -p "\\$\{parameter/pattern/string}"' bash

2. +=运算符

有一天,我看到这样一个用法:

$ arr=(1 2 3)
$ arr+=(4 5)

原来数组还可以这样相加,后来我看了下Bash的手册,确实有一段这么说明的,这里未引用这段文字,有兴趣的可以查看Bash Reference Manual。 自然地我们会想到如果一个变量是数字,是否也可以用+=作运算呢?

$ i=1
$ i+=1

但是,运行后你会发现i的结果并不为2,而是11,这里bash并不认为i是一个整数,而是作为字符串。 这时可以通过declare声明一个变量为整数,上面的问题就解决了:

$ declare -i int=1
$ int+=1
$ echo $int
2

3. Here document不为人知的用法

Shell学得越多,越会发现一些神奇的用法,每天都觉得自己实在是一个刚入门的菜鸟。

一般的here document的用法是这样的:

$ cat b.sh
cat<<EOF
hello, $USER
EOF
$ sh b.sh
hello, kodango

here document中的变量都是会被展开的,那能不能不展开呢?答案是可以的,将EOF有引号括起来就可以:

$ cat b.sh
cat<<"EOF"
hello, $USER
EOF
$ sh b.sh
hello, $USER

一般here document用得最多的是在帮助函数(helpusage)函数里面,因为在这里我们要写一大段的脚本用法。

如果你有强迫症(比如我),有时候使用here document的时候会很不爽,因为here document里面每行首部的空格都会被保留,而如果要顶格写,在缩进的地方又会有点打乱结构,例如:

$ cat b.sh
# part 1
if :; thencat << EOFhello, $USER
EOF
fi# part 2
if :; thenif :; thencat << EOF
hello, $USER
EOFfi
fi

上面的脚本执行的结果为:

$ sh b.sh hello, kodango   # part 1 result
hello, kodango       # part 2 result

有没有办法既兼顾到缩进又能不保留行首空格呢?

答案也是肯定的,只不过语法又要稍稍变一下,现在在<<的后面加一个短横,这个用法下,行首的Tab字符都会被忽略:

$ cat b.sh
if :; thencat <<- EOFhello, $USER
EOF
fi
$ sh b.sh
hello, kodango
fi

一定要是Tab键哦,空格也是不可以的,在vim里面还要注意如果设置了smarttab选项,行首插入的Tab键会替换成相应个数的空格(这里可以按ctrl+v tab插入实际的空格)。

关于这一节的内容,可以进一步参考[Redirection#here_documents [Bash Hackers Wiki]](http://wiki.bash-hackers.org/syntax/redirection#here_documents)。

4. 使用内置命令declare显示脚本中定义的函数

declare的-F选项可以列出脚本中定义的函数名称:

$ cat b.sh
function one()
{:
}function two()
{:
}declare -F | sed 's/declare -f //'
$ sh b.sh
one
two

5. 嵌套函数还可以这么用

Bash中可以嵌套函数定义,即在一个函数中定义另外一个函数,例如:

[root@localhost ~]# cat nest.sh
#!/bin/bashfunction out()
{echo "out"function inner() {echo "inner"}
}inner
out
inner

这里out函数里面定义了inner函数,形成嵌套函数。但是,执行上面的例子会出错(nest.sh: line 12: inner: command not found),这是因为这是后inner函数还没定义。一旦out函数执行之后,inner函数就被定义了。整个例子的执行结果是这样的:

[root@localhost ~]# sh nest.sh
nest.sh: line 12: inner: command not found
out
inner

看到这里,你可能会想嵌套函数有什么用?事实上,在大多数情况下,我们基本不会用到嵌套函数。但是它并非一无是处,比如下面的例子就向我们展示了嵌套函数的神奇用法。

假设,我们要定义一个调试函数,同时需要一个开关控制该函数是否输出调试日志,最简单的写法是:

function log()
{if [ "$verbose" = "1" ]; thenecho "$@"fi
}

它可以完成任务,但是唯一美中不足的是,每次调用该函数都要判断verbose的值是否为1。这时候可以使用嵌套函数来弥补这个不足:

#!/bin/bashverbose=${1:-1}function log()
{if [ $verbose -eq 1 ]; thenfunction log() {echo "$@"}echo "$@"elsefunction log() {:}fi
}log what is your name
log my name is kodango

上面的例子中,根据verbose的值定义了两个同名的log函数来覆盖之前的旧函数,以后调用的函数就都是后定义的函数了。

6. 删除ps auxf | grep python结果中的grep进程

在shell脚本中,经常需要利用ps和grep命令一起在查找进程相关的信息,尤其是针对python/java/shell等脚本进程,因为pidof本身不大支持查找脚本进程对应的pid。

在用ps auxf | grep python的时候,一个很恼人的事情是,经常会出现多余的grep进程:

$ ps auxf | grep python
kodango    18832  0.0  0.0 674192 10444 ?        Sl   23:19   0:00  python test.py
kodango    63860  0.0  0.0  61180   752 pts/2    S+   23:28   0:00  grep python

所以我们需要再加一个grep -v grep来排除它。

之前一直弄不明白为什么会这样,今天在看BashPitfalls的时候,终于明白原因了,stackoverflow上也有一个回答解释得很好。

shell在执行以上命令的时候,其实创建了一个管道,并且fork了两个子进程:ps auxf与grep python,并且将管道读的这一端绑定到grep的标准输入,管道写的这一段绑定到ps的标准输出。ps将自己的输出写到管道,grep从管道中读取输入。可能在这个时候,ps与grep是同时执行的,所以ps的结果中也会包含grep进程的信息。

还有一个解决方法是巧用正则表达式:

$ ps auxf | grep [p]ython

7. Shell如何实现timeout功能

有时候我们不希望某个命令执行太久,所以如果在给定的时间内没有完成,能够杀掉这个命令对应的进程,这就是timeout功能,可惜bash没有提供该功能。所以就得我们自己来实现。

实现代码如下所示:

function timeout()
{local time cmd pidif echo "$1" | grep -Eq '^[0-9]+'; thentime=$1shift && cmd="$@"elsetime=5cmd="$@"fi$cmd &pid=$!while kill -0 $pid &>/dev/null; dosleep 1let time-=1if [ "$time" = "0" ]; thenkill -9 $pid &>/dev/nullwait $pid &>/dev/nullfidone
}

假设有一个测试脚本sleep.sh:

$ cat sleep.sh
echo "sleep $1 seconds"
sleep $1
echo "awake from sleep"

现在利用我们写的timeout函数来达到超时kill功能:

$ time sh timeout.sh 2 'sh sleep.sh 100'
sleep 100 secondsreal    0m2.005s
user    0m0.002s
sys    0m0.001s

看最终执行的时间,差不多就是2秒钟。

上面timeout函数实现的代码中,利用了两个技巧:

  • kill -0 $pid:发送信号0给进程,可以检查进程是否存活,如果进程不存在或者没有权限,则返回错误,错误码为1;
  • wait $pid &>/dev/null:等待某个进程退出返回,这样相对比较优雅,同时将错误重定向到黑洞,从而隐藏后台进程被kill的错误输出;

8. 利用/etc/inittab实现watchdog

还在为实现watch dog而头疼吗,其实inittab中已经包含了该功能。可以将自己的脚本或者程序写到inittab文件中:

tt:2345:respawn:/home/kodango/sleep.sh 100

然后执行telinit q使其生效,ps看下该脚本是否已经在运行了,尝试kill后,又会被起起来。

9. 慎用波浪号展开

在shell中对比下面两种用法:

$ home1=~kodango
$ home2="~kodango"
$ echo -e "$home1\n$home2"
/Users/kodango
~kodango

第一个变量赋值,波浪号正确展开,所以我们得到了kodango用户的家目录地址;第二个变量,我们使用了双引号,这个时候波波浪号并没有展开。这是一个比较容易出错的地方。

还有一点要注意的地方是,波浪号展开只在:或者=号后面才会执行。所以:

$ path=1~kodango
$ echo "$path"
1~kodango$ path=1:~kodango
$ echo "$path"
1:/Users/kodango

为什么要在:后面也可以展开呢?想想PATH的定义吧。

$. 最近淘到的一些实用的shell文章

  • BashPitfalls - Greg's Wiki
  • ProcessManagement - Greg's Wiki
  • BashGuide - Greg's Wiki
  • BashFAQ - Greg's Wiki

简洁的 Bash Programming 技巧(三)相关推荐

  1. Shell编程:简洁的 Bash Programming 技巧(三)

    这是简洁的 Bash Programming 技巧系列的第三篇文章,这一系列的文章专门介绍Bash编程中一些简洁的技巧,帮助大家提高平时 Bash 编程的效率.有兴趣的同学可以回顾下之前的两篇文章(一 ...

  2. Shell编程:简洁的 Bash Programming 技巧续篇

    简洁的 Bash Programming 技巧系列文章专门介绍Bash编程中一些简洁的技巧,帮助大家提高平时 Bash 编程的效率.继上一篇文章发布后,收到很多读者的反响,所以我决定继续将自己学到的一 ...

  3. Shell编程:简洁的 Bash Programming 技巧

    简洁的 Bash Programming 技巧这一系列文章专门介绍Bash编程中一些简洁的技巧,帮助大家提供 Bash 编程的效率,目前该系列已经有三篇文章,有兴趣的同学可以继续阅读其它两篇续篇(一) ...

  4. Coursera系列-R Programming第三周-词法作用域

    完成R Programming第三周 这周作业有点绕,更多地是通过一个缓存逆矩阵的案例,向我们示范[词法作用域 Lexical Scopping]的功效.但是作业里给出的函数有点绕口,花费了我们蛮多心 ...

  5. Linux Shell常用技巧(三) sed

    Linux Shell常用技巧(三) sed 八.流编辑器sed 8.1 sed简介 sed是stream editor的缩写,一种流编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区 ...

  6. 79. Leetcode 871. 最低加油次数 (堆-技巧三-事后小诸葛)

    技巧三 - 事后小诸葛这个技巧指的是:当从左到右遍历的时候,我们是不知道右边是什么的,需要等到你到了右边之后才知道.如果想知道右边是什么,一种简单的方式是遍历两次,第一次遍历将数据记录下来,当第二次遍 ...

  7. 百万数据查询优化技巧三十则,新增5条

    百万数据查询优化技巧三十则 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判 ...

  8. 工作效率提升技巧三:做事的心态

    上升高度的东西,很多人都不喜欢.比如道理.原理.思考.态度,等等. 有时候,最没用的东西是这些上升高度的东西.有时候,最有用的东西也是这些上升高度的东西. 今天,我们就从上升高度中的"心态& ...

  9. 说话技巧三步曲--摘自《所谓高情商 就是会说话》之七个突破口

    从上篇文章 说话技巧三步曲--摘自<所谓高情商 就是会说话>   中大家发现了吧,一个最关键的步骤就是第二部,怎么揣摩对方的心理,并且找到突破口. 佐佐木圭一给我们总结了七个突破点 1. ...

最新文章

  1. vue实现文件上传功能
  2. 指尖上的敏捷 – leangoo微信版全新上线
  3. cjuiautocomplete ajax,$ _GET( '术语' 在CJuiAutocomplete的Widget
  4. AE开发使用内存图层
  5. 【AI超级美发师】深度学习算法打造染发特效(附代码)
  6. ASP.NET Core微服务(二)——【ASP.NET Core Swagger配置】
  7. springboot创建多个对象
  8. gif android 点击 加载,android 加载显示gif图片的解决方案
  9. 计算机学业水平测试字处理多少分,【计算机应用论文】计算机应用基础学业水平的测试问题(共3624字)...
  10. 考研编程练习----畅通工程
  11. Seata多微服务互相调用_全局分布式事物使用案例_@GlobalTransactional验证---微服务升级_SpringCloud Alibaba工作笔记0065
  12. java equals getclass_Java equals()方法 – 子类中equals的语义如何确定getClass和instanceof的使用...
  13. [HNOI2002]营业额统计 Splay tree入门题
  14. spark配置IntelliJ开发环境详解
  15. 由PPP项目总结的几点项目经验
  16. DES算法理解--附《密码编码学与网络安全(第七版)》课后练习题答案
  17. quartz 每月一次_Quartz 每月1号,执行规则表达式怎么列?
  18. 计算机毕业论文选题影视方向,毕业设计选题方向
  19. mysql数据库查询总条数
  20. 华为网络设备加固各种基线命令配置

热门文章

  1. Android开发 ---多线程操作:Handler对象,消息队列,异步任务下载
  2. [SPOJ DQUERY] D-query(树状数组,离线)
  3. sublime代码片段
  4. 关于string,我今天科普的
  5. ThinkPHP add、save无法添加、修改不起作用
  6. 非常恶俗地分享一首歌曲(子陵·周郎顾)
  7. python 3的33个保留字列表_python 33个保留字是什么意思
  8. 计算机应用基础0006 19秋在线作业2,川大《计算机应用基础0006》13春在线作业2
  9. java 会话共享_java – servlet如何工作?实例化,会话,共享变量和多线程
  10. Java中lombok @Builder注解使用详解