一个小脚本背后的shell script细节

0. 引子

事情的起因是因为公司目前在用的即时通讯软件里可用的表情太少,所以想导一份百度的泡泡表情进去。

看起来这玩意可以下载的地方还挺多的,但实际下载完发现下载站上的大小都偏大,于是就想是不是直接从百度贴吧里批量下载。

image.png

查看元素发现表情的url还是很有规律的,不用上正则了。在shell下写脚本就够用了,很快就写好了一个:

!/bin/sh
if [ ! -d "./bdpao/" ]
then
   mkdir bdpao
else
   echo "folder existed"
fi
#我只想要黄脸表情,不需要其他的物件表情,所以把值设置为33
for (( i=1; i<=33; i++ ))
do
   if [ "$i" -le 9 ]
   then
       dlurl="http://tb2.bdstatic.com/tb/editor/images/face/i_f0"$i".png?t=20140803"
       wget -q "$dlurl" -O ./bdpao/0$i.png
   else
       dlurl="http://tb2.bdstatic.com/tb/editor/images/face/i_f"$i".png?t=20140803"
       wget -q "$dlurl" -O ./bdpao/$i.png
   fi
done
zip -q -r bdpao.zip ./bdpao

然而实际运行时发现的问题,让我意识到自己对shell script的认识仍停留在一个一知半解的阶段。

1. 不同的循环风格

关于shell中常用的for循环的风格,根据Linux shell 用for循环100次的方法介绍的内容主要有C风格、Python风格(使用in)及使用seq。

1.1. C风格

我在编写脚本时,是先用C风格的for循环实现的,本来以为直接sh bdpao.sh就可以跑,但是实际运行时报了一个这样的错:

bdpao.sh: 8: bdpao.sh: Syntax error: Bad for loop variable

stackoverflow里有人也提了类似的问题syntax-error-bad-for-loop-variable,这里的解答说的比较清楚:

The for (( expr ; expr ; expr )) syntax is not available in sh.

即是说C风格在sh是不可用的。

解决方案:可以用bash bdpao.sh,或者改成while循环再用sh bdpao.sh运行。

1.2. Python风格

继续试了试Python风格,sh bdpao.sh后报了和之前不一样的错:

bdpao.sh: 11: [: Illegal number: {1..33}

但换成bash可以运行,说明Python风格在sh下也不可用。

1.3. 使用seq

那么seq是不是也是一样呢?我把脚本修改成使用seq的形式,仍然报错了:

bdpao.sh: 12: [: Illegal number: seq 1 33

不仅如此,bash bdpao.sh时,继续报错:

./bdpao.sh: line 12: [: seq 1 33: integer expression expected

为什么会这样呢,难道sh和bash都不支持seq,那为什么还有很多shell的教程里用seq做循环?

原来这里我犯了一个最大的错误,但也是小白最大的陷阱,就是是’与`的区别,我在一知半解下把反引号当成单引号!但这两者意义完全不同。

(())与()还有${}差在哪?在bash shell中, $()与``(反引号)都是用来做命令替换(command substitution)的。所谓的命令替换是通过完成``或者$()里面的 命令,将其结果替换出来,再重组命令行。例如:

$ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)

将’改正为`后,用sh命令和bash命令都可以运行。

2. sh,./,还是bash?

问题好像是解决了,但之前看《UNIX/Linux/OS X中的Shell编程(第4版)》里讲循环的章节,都是在shell下使用类似for i in {1..33}形式的循环写法。我在自己的linux试了一下也是可以,为什么把内容写进.sh文件里就会有这些问题?执行.sh脚本的时候,什么时候应该用sh,什么时候应该用bash?

what-is-the-purpose-of-the-sh-command中有朋友解答了这一疑问:使用sh命令的目的是通过这一命令来指定运行环境。这种C风格、Python风格的for循环可以在shell下直接使用的原因是,大部分系统默认使用的shell是/bin/bash,这一点可以通过查看当前使用的shell来验证。

通过echo $SHELL可以发现系统默认的shell是/bin/bash,通过echo $$查当前shell的进程号,结果如下:

mint64@mint64-virtual-machine ~/Documents $ echo $$
2265
mint64@mint64-virtual-machine ~/Documents $ ps -ef | grep '2265'
mint64     2265   2258  0 Sep26 pts/6    00:00:01 bash
mint64    44980   2265  0 15:56 pts/6    00:00:00 ps -ef
mint64    44981   2265  0 15:56 pts/6    00:00:00 grep --color=auto 2265

当前系统使用的也确实是bash,这种通过查shell进程号确认当前使用shell的操作可以通过grep与awk的组合来做:

ps | grep $$ | awk '{print $4}'

2.1. 三种情况

那么我们究竟应该如何选择shell脚本的执行命令呢?据difference-between-and-sh-in-unix的解答,使用sh file格式执行shell script时会新建一个shell进程,使用. file格式时是在当前的shell进程中执行shell脚本文件,而使用./file则是在当前目录下执行文件。

解答中还很贴心的举例说明了这三种情况,如果有一个名为test.sh的shell脚本文件,其内容为:

#!/bin/sh

TEST=present

使用sh test.sh执行时,会运行一个新的sh,然后把脚本里的变量在这个sh中定义,最后退出。因此退出后如果用echo $TEST将没有结果输出,因为这个变量仅在sh test.sh时使用的sh里,在外部的shell不存在。

如果用. test.sh执行,echo $TEST可以输出present的结果,因为这种方式是在当前的shell中执行shell脚本文件,而不是像sh test.sh一样新建一个sh。

如果用./test.sh执行,这时#!/bin/sh被检测,等价于/bin/sh ./test.sh

即是说,如果不需要指定运行环境的情况下,shell脚本文件的头部不加shebang的内容也是可以的。

3. 小结

现在网上不少教程只告诉你怎么做,并不会告诉你是为什么,所以网上搜到的海量内容不等同于自己的知识,只有甄别、消化乃至加工后的内容才能称得上是真正掌握。

最后,从需求入手永远是学习技术最快的一种方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值