shell脚本直接执行与被调用执行结果不一致问题定位及总结

本文主要总结近日在完成工作时遇到的一个shell脚本问题。具体问题是:shell脚本直接执行与被其他程序调用看到的执行结果不一致。
如果没时间看全文,就直接看结论:

  • 脚本相关的问题,首先考虑是不是用户不对(权限不对)
  • 脚本相关的问题,其次考虑是不是调用的路径不对
  • 定位时最好把错误日志打印到日志文件中(需要用到2>&1),根据出错日志来,问题可能会很快解决

问题起因

最近完成工作时,需要调用一个脚本,但是脚本中的有一段内容是固定的,因此需要在调用前获取环境的信息来替换脚本之中的内容后再执行该脚本,简单的说,比如需要获取环境中实际的IP来替换掉脚本中写死的IP或一个字符。于是就写了一个脚本来完成这个任务。

问题现象

完成脚本之后,在本地进行了调试,为了方便描述,分别将两个脚本简化为task1.sh和task2.sh,并且两个脚本在同一个目录下。

task1.sh内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

#do some task

#now get current equipment IP
current_IP=`ifconfig | grep "inet" | sed -n '1p' | awk '{print $2}'`

sed -i "s/${current_IP}/127.0.0.1" test2.sh

sh test2.sh

echo "current IP:${current_IP}."

test2.sh内容:

1
2
3
4
5
#!/bin/bash

#do some task

do something:127.0.0.1

在完成好如上内容之后,直接执行sh test1.sh,运行结果也ok,符合预期。

然后将整个流程一起跑,发现运行结果不对,打印到屏幕上的结果也不对。
也就是直接调用脚本的结果是ok的,但是别人来调用我的脚本结果却不对。

为什么自己直接登录到环境上执行那个task1.sh脚本是对的,整个流程中,其他程序比如lua或者c程序来调用,结果却不对呢,百思不得其解。

问题分析及解决过程

为了搞清是哪不对,对上述脚本进行分解。打印那句话很关键,发现打印到屏幕上的是
current IP:.
说明current_IP为空,那肯定是后面的整个语句的结果为空。
而那语句很长又有grep命令,有两种可能:

  1. ifconfig命令出错,按道理这个命令很常见,出错可能性很小,而且单独直接运行都可以
  2. 后面grep等语句出错

为了定位到原因,将task1.sh脚本改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

#do some task

#now get current equipment IP
ifconfig > IP.txt
current_IP=`cat IP.txt | grep "inet" | sed -n '1p' | awk '{print $2}'`

sed -i "s/${current_IP}/127.0.0.1" test2.sh

sh test2.sh

echo "current IP:${current_IP}."

主要是将ifconfig命令的结果重定向到文件中,然后又分别试了直接执行和由流程调用,仍然发现直接执行OK,流程调用不行。
直接执行发现当前目录下生成了IP.txt,流程调用,在脚本的路径下却没有发现IP.txt,使用命令find / -name IP.txt发现该IP.txt在目录/opt/myapp/bin/目录下。一想在这也正常,因为C程序是在当前目录调用task1.sh的,本以为问题解决,是路径不对的问题导致,但是打开IP.txt文件一看,里面也没有任何信息。随即一想,是不是用户没有权限,然后脚本改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

#do some task

#now get current equipment IP
basepath=$(cd `dirname $0`; pwd)
user=`whoami`
ifconfig > /opt/myapp/script/IP.txt
current_IP=`cat /opt/myapp/script/IP.txt | grep "inet" | awk '{print $2}' | awk -F [":"] '{p rint $2}'`

sed -i "s/${current_IP}/127.0.0.1" test2.sh

sh test2.sh

echo "current IP:${current_IP}, current user:${user}, path:${basepath}"

这样修改之后,再用流程调用发现用户也是root,但是为啥不对呢,为啥IP.txt里面没有生成任何信息呢。

最后,将脚本改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

#do some task

#now get current equipment IP
basepath=$(cd `dirname $0`; pwd)
user=`whoami`
ifconfig > /opt/myapp/script/IP.txt 2>&1
current_IP=`cat /opt/myapp/script/IP.txt | grep "inet" | awk '{print $2}' | awk -F [":"] '{p rint $2}'`

sed -i "s/${current_IP}/127.0.0.1" test2.sh

sh test2.sh

echo "current IP:${current_IP}, current user:${user}, path:${basepath}"

然后继续用流程调用,发现IP.txt里面内容为:
ifconfig: command not found

终于知道关键问题在哪了o( ̄︶ ̄)o:

原因:ifconfig命令所在路径/sbin未包含在系统环境变量PATH中(遇到其他命令出现这种情况可以参考下述解答,举一反三)

解决方法:

  1. 直接输入:/sbin/ifconfig
  2. 临时修改环境变量:在shell中输入
    $export PATH = $PATH:/sbin
    然后再输入ifconfig命令即可,但是这只是临时更改了shell中的PATH,如果关闭shell,则修改消失,下次还需要重复如上操作
  3. 永久修改PATH变量使之包含/sbin路径:
    打开/etc/profile文件,在其中输入export PATH=$PATH:/sbin,保存并重开一个Xshell即可,这样一来,PATH路径永久修改成功,以后任何时候只需书序ifconfig命令即可

我使用方法1解决问题,直接写全路径,随便把/sbin加入到PATH中,可能会引起一些不必要的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

#do some task

#now get current equipment IP

current_IP=`/sbin/ifconfig | grep "inet" | awk '{print $2}' | awk -F [":"] '{p rint $2}'`

sed -i "s/${current_IP}/127.0.0.1" test2.sh

sh test2.sh

echo "current IP:${current_IP}."

总结

几个基本符号及其含义
/dev/null 表示空设备文件
0 表示stdin标准输入
1 表示stdout标准输出
2 表示stderr标准错误
2>&1,2就是标准错误,1是标准输出,那么这条命令就相当于把标准错误重定向到标准输出

  • 最初没有找到问题的关键,是因为没有将错误日志打印出来(用2>&1就可以了),然后在进行瞎猜
  • 如果遇到脚本执行不如预期,首先需要考虑是不是用户不对(权限不对)或者路径不对
  • 获取当前用户:whoami
  • 获取当前路径:basepath=$(cd dirname $0; pwd)

如果一开始就把错误日志重定向到日志文件中,那么解决这个问题就是分分钟的事情了。
此外说明经验也很重要,如果经验丰富,多半能猜到是不是环境变量问题导致那个命令没有被找到,因此没有获取到想要的信息。

注意:ifconfig|grep “inet”|sed -n ‘1p’|awk ‘{print $2}’这个命令要根据环境随机变化

参考资料

Linux里的2>&1究竟是什么

如果你觉得本文对你有帮助,欢迎打赏