bash脚本执行失败问题

背景:我们的项目使用了superset(Airbnb开源的数据挖掘平台),superset的运行需要python2.7环境,以前项目部署时候都是手动安装python2.7,然后再安装产品。最近接了一个自动升级python的需求,在开发过程中被linux的环境变量坑了一会。

  • 问题1:Python2.7安装完后,为什么执行python -V 还是2.6
  • 问题2:安装过程中进行Python编译,包make命令command not found是什么鬼?

Linux的环境变量一般是这样的(执行echo $PATH):/sbin:/usr/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/usr/games:/opt/setsuse/SEK/Tcl

其中/usr/local/bin放的的用户安装软件的命令目录,/usr/bin放的是系统自带软件的命令目录。
也就是说,执行命令的时候会先/usr/local/bin中查找命令是否存在,存在则执行,不存在再继续往下一个目录查找。如果所有目录都找不到命令,就会报命令找不到错误。(所以一般出现command not found,先检查环境变量)

但是如果处于各种各样的原因,(人工或者一些系统配置)修改了环境变量,或者多个目录下存在命令的情况,那么命令执行就会出现奇怪的问题了。

以上背景内容可忽略

为什么安装完Python2.7后执行python -V还是2.6

因为/usr/bin中有一个python软链接,指向Python2.6(系统自带),如果在命令行直接执行python -V,会先在/usr/bin执行python(指向Python2.6),所以调用到的其实还是Python2.6。

解决方案:

方法1:执行命令的时候加上安装目录

/usr/local/bin是python2.7的默认安装目录,如果安装时候更改了安装目录,则换成安装的那个目录

1
2
/usr/local/bin/python -V
Python 2.7.2

方法2:修改/usr/bin下的python软链接,让其执行python2.7

1
2
3
4
rm /usr/bin/python
ln -s /usr/local/bin/python2.7 /usr/bin/python
python -V
Python 2.7.2

为什么安装过程中sudo执行bash脚本来编译Python包,提示sudo make command not found

因为linux使用sudo执行脚本,在运行脚本内容的shell中,环境变量发生了改变。
做个实验:写一个bash脚本path.sh,内容如下:

1
2
#!/bin/bash
echo $PATH
1
2
3
4
sudo ./path.sh
/usr/bin:/bin:/usr/sbin:/sbin
sudo echo $PATH
/sbin:/usr/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/usr/games:/opt/setsuse/SEK/Tcl

直接执行sudo和sudo执行脚本使用的环境变量是不一样的。这就是sudo执行脚本找不到命令的原因。
因为linux在sudo执行脚本的时候,会将shell的环境变量重置。(下方的env_reset配置)

1
2
3
4
5
6
7
8
9
sudo -l
Matching Defaults entries for root on this host:
always_set_home, env_reset, env_keep=”LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY
LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE”, targetpw
Runas and Command-specific defaults for root:
Defaults>root syslog=auth
User root may run the following commands on this host:
(ALL) ALL
(ALL) ALL

解决方案:各有利弊

方案1:在脚本中加入环境变量配置

1
2
3
4
5
6
7
#!/bin/bash
PATH=$PATH:/usr/local/bin/:/usr/local/sbin
export PATH
echo $PATH
sudo ./path.sh
/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/:/usr/local/sbin
##/usr/local/bin/:/usr/local/sbin为在脚本中配置的环境变量

优点:方便快捷
弊端:如果在脚本中,使用sudo 去调用其他脚本,其他脚本也要再次配置$PATH,否则一样会找不到命令

方案2:使用sudo执行脚本带上“-E”参数,则执行脚本内容时会带上当前shell的环境变量

1
2
sudo -E ./path.sh
/sbin:/usr/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/usr/games:/opt/setsuse/SEK/Tcl

优点:方便快捷
弊端:同样的,如果在脚本中需要sudo调用其他脚本,也要带上“-E”参数

方案3:在脚本中执行命令带上安装目录,就像上个问题中解决python找不到2.7问题是一样的原理。

优点:一般不会再出现command not found问题
弊端:增加脚本复杂度,每个命令都要知道其安装目录

方案4:在/usr/bin中使用软链接,跳转到命令所在目录。就像上个问题中增加python软链接到python2.7一样。

优点:一般不会再出现command not found问题,而且其他地方也可以使用。
弊端:需要提前知道要使用的命令,当时加上一次之后,所有脚本都可以使用。


为什么unzip解压命令没有用?

在一个安装python的脚本需要解压文件进行编译,但是发现第一次执行解压、编译没问题,但是后面就编译失败了,苦思无解。幸好有个大神指出了问题所在。
在bash脚本执行写作过程中,特别要注意一些 有可能 需要交互的命令。

例如unzip,如果解压文件已存在,则会提示是否进行覆盖,此时如果没有在bash脚本中进行输入,则会shell会一直等待输入至命令超时(命令等待超时后的动作视不同实现决定)。

解决方案:

  • 熟悉命令的不同参数,一般都会有规避方法。例如unzip 有参数-o 表示强制覆盖
  • 也有一些自动输入的方式,例如
1
echo 'abc' | unzip xxxx

为什么有一些脚本执行到一半就失败了?

在执行升级ptyhon的bash脚本过程中,执行config到一半就停止了,没有任何报错。折腾半天也没明白,config这条linux命令为什么会执行到一半戛然而止。
幸好大神一眼洞穿,表示踩过这个坑。

在执行bash脚本的过程中,shell的缓存区是有限大小(据说是1k),但缓冲区占满后,命令执行会中止。

因为我的脚本中编译python整个过程有很多打印,所以导致了缓存区占满了。

解决方案:

一般bash脚本中执行命令会选择把输入打印到文件,一方面避免了缓存区占满问题,一方面也有利于后续问题定位。

1
./config >> /var/log/xx.log