empty:expect的替代

之前为了能够处理命令交互,编译了静态链接的expect,结果遇到了大问题导致在没有tcl的机器上还是无法运行。找了半天,发现empty是一个不错的替代。

优势

首先,empty是一个纯C实现的,不依赖任何其他库的工具。这对于需要在不同机器上分发执行非常关键,之前expect就是因为无法确保机器上都有tcl或者其初始化脚本,导致无法使用。

其次,empty使用非常简单,可以方便的嵌入到shell脚本中,而无须像expect那样使用单独的脚本。这能够大大降低学习成本和后续维护成本。

使用

empty和一般应用安装方式类似,直接make即可。整个源码包中核心只有一个C文件,结构非常简单。下载地址在:http://sourceforge.net/project/showfiles.php?group_id=136798 这里。安装方式可以参考项目首页命令。

编译完成之后,就可以直接使用empty这个可执行程序了。它的help输出不是很友好,比较简单,大致参数有这几个:

  • -f:应该是fork的意思吧,这个参数表示会执行后面的命令,同时这个命令可以通过-i和-o指定命令的输入和输出管道。如果不指定。empty会以当前进程id和子进程id作为标识生成对应的文件。
  • -w:应该是watch的意思吧,该参数可以执行监听的标准输入管道(一般为-f的标准输出管道)和写入的标准输出管道(一般为-f的标准输入管道),然后后续以键值对的方式追加多个监听的key,和输出的value,即发现标准输入包含key时,向标准输出写入value。这个键值对可以有多个。
  • -i:标准输入对应的管道文件。对于-f参数指定的是执行命令的标准输入,后续可以通过这个标准输入管道,向执行命令发送标准输入内容。特别注意,对于-w参数这里表示监听输入的来源,需要设置为-f的标准输出管道。
  • -o:标准输出对应的管道文件。对于-f参数指定的是执行命令的标准输出,后续可以通过读取这个管道文件,监听命令的输出。对于-w参数,这里表示输出写入的管道,需要设置为-f的标准输入管道。
  • -s:应该是send的缩写吧,可以直接指定向标准输出管道(-o)写入字符
  • -t:设置watch的超时时间,如果超时时间内没有输入,则退出empty命令。默认值为0,表示不超时。

因此,大部分场景下,先通过-f启动命令,然后通过-w命令监视输出,并且写入结果。例如代码中的示例,如果要模拟ssh登录,可以这样:

ssh="ssh"                               # (/full/path/to/)ssh
target="localhost"                      # target host
login="luser"                           # username (Change it!)
password="TopSecret"                    # password (Change it!)

fifo_in="/tmp/empty.in"                 # input fifo
fifo_out="/tmp/empty.out"               # output

# -----------------------------------------------------------------------------
cmd="$ssh $login@$target"
tmp="/tmp/empty.tmp"                    # tempfile to store results

echo "Starting empty"
empty -f -L $tmp -i $fifo_in -o $fifo_out $cmd
if [ $? = 0 ]; then
        if [ -w $fifo_in -a -r $fifo_out ]; then
                echo "Sending Password"
                empty -w -v -i $fifo_out -o $fifo_in -t 5 assword: "$passwordn"
                echo "Sending tests"
                empty -s -o $fifo_in "echo "-- EMPTY TEST BEGIN --"n"
                empty -s -o $fifo_in "uname -an"
                empty -s -o $fifo_in "uptimen"
                empty -s -o $fifo_in "who am in"
                empty -s -o $fifo_in "echo "-- EMPTY TEST END --"n"
                echo "Sending exit"
                empty -s -o $fifo_in 'exitn'
                echo "Check results:"
                sleep 1
                cat $tmp
                rm -f $tmp
        else
                echo "Error: Can't find I/O fifos!"
                return 1
        fi
else
        echo "Error: Can't start empty in daemon mode"
        return 1
fi

echo "Done"

这么一大堆脚本,其实就三行和empty使用有关,一个是empty -f -L $tmp -i $fifo_in -o $fifo_out $cmd,该命令执行ssh命令,并且指定输入、输出和日志文件。empty -w -v -i $fifo_out -o $fifo_in -t 5 assword: "$password\n"该命令通过监听ssh命令的输出,如果遇到“assword:”字样,则向ssh进程发送密码。注意,这里的-i和-o参数和之前相反,因为需要监听ssh命令的输出,向ssh命令的输入发送字符。最后就是empty -s -o $fifo_in "echo \"-- EMPTY TEST BEGIN --\"\n"这时ssh已经认证成功了,因此就直接向远程写入通过ssh执行的命令即可。

除了这种预先知道命令每一步的输出之外,还有一些场景是命令可能在有些场景下会进行交互,有些场景下不会。我们遇到的场景就是webx提供的autoconfig命令。该命令默认会在缺少配置项的时候进行交互,提示用户是否使用默认值,并且写入到antx.properties文件。

这时,就不能简单的通过每一步等待特定的字符,然后输出了。这里我们利用了empty的另一个特性,即自动维护管道文件,命令开始执行时,empty会创建对应的标准输入和标准输出两个管道文件,命令结束时删除,这样就不用用户来维护命令的输入输出,也不会产生垃圾文件。因此,我们可以通过循环的方式,监听是否有我们需要的关键字,在每次监听前,增加判断管道文件是否存在,以防止命令已经正常退出。大致的脚本如下:

empty -f -i input -o output -L $HOME/autoconfig.log autoconfig -u $HOME/antx.properties $f
sleep 1
while true;
do
    if [ -w input -a -r output ]; then
        empty -w -i output -o input -t 5 ".*Yes.*" "yesn" ".*[Quit.*" "quitn"
    else
        break;
    fi
done

while循环里面,首先需要判断的是input和output两个文件是否存在。对于input文件,通过-w检查文件是否存在的同时,检查文件是否可写;对于output文件,通过-r检查文件是否存在的同时,检查文件是否可读。如果两个文件都存在,那么运行empty命令,等待输出并且设置超时时间。

这样的实现有两个好处,首先是可以兼容命令正常运行的流程,即无须进行交互。其次是不需要知道命令当前的状态,我们在一个empty命令里面已经兼容了命令所有可能的输出对应的用户输入值。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.