"read" 命令未在 "while read line" 循环中执行

"read" command not executing in "while read line" loop

首先post这里!我真的需要这方面的帮助,我在 google 上查看了这个问题,但找不到对我有用的答案。所以这就是问题所在。 我在 bash 中编写一些类似框架的代码很有趣。每个人都可以创建自己的模块并将其添加到框架中。但。为了知道脚本需要什么参数,我创建了一个 "args.conf" 文件,它必须在每个模块中,看起来像这样:

LHOST;true;The IP the remote payload will connect to.
LPORT;true;The port the remote payload will connect to.

第一列是参数名称,第二列定义是否需要,第三列是描述。不管怎样,长话短说,框架应该逐行读取 args.conf 文件,向用户询问每个参数的值。这是一段代码:

info "Reading module $name argument list..."
while read line; do
    echo $line > line.tmp
    arg=`cut -d ";" -f 1 line.tmp`
    requ=`cut -d ";" -f 2 line.tmp`
    if [ $requ = "true" ]; then
        echo "[This argument is required]"
    else
        echo "[This argument isn't required, leave a blank space if you don't wan't to use it]"
    fi
    read -p " $arg=" answer
    echo $answer >> arglist.tmp
done < modules/$name/args.conf
tr '\n' ' ' < arglist.tmp > argline.tmp
argline=`cat argline.tmp`
info "Launching module $name..."
cd modules/$name
$interpreter $file $argline
cd ../..
rm arglist.tmp
rm argline.tmp
rm line.tmp
succes "Module $name execution completed."

如您所见,它应该向用户询问每个参数的值...但是:

1) 读取命令似乎没有执行。它只是跳过它,参数没有值

2) 尽管 args.conf 文件包含 3 行,但循环似乎只执行了一次。我在屏幕上看到的只是一次“[此参数是必需的]”,模块刚刚启动(并且崩溃,因为它没有所需的参数......)。

真的不知道怎么办,在这里...希望这里有人能回答^^'。 提前致谢!

(对于最终的错误,我很抱歉,我是法国人)

阿尔法。

正如@that other guy 在评论中指出的那样,问题是循环中的所有 read 命令都是从 args.conf 文件而不是用户读取的。我处理这个问题的方法是通过与 stdin (fd #0) 不同的文件描述符重定向 conf 文件;我喜欢为此使用 fd #3:

while read -u3 line; do
    ...
done 3< modules/$name/args.conf

(注意:如果您的 shell 的 read 命令不理解 -u 选项,请改用 read line <&3。)

我不建议在此脚本中使用其他一些东西:

  • 不带双引号的变量引用,例如echo $line 而不是 echo "$line"< modules/$name/args.conf 而不是 < "modules/$name/args.conf"。未加引号的变量引用被拆分成单词(如果它们包含空格),任何恰好与文件名匹配的通配符将被匹配文件列表替换。这可能会导致 really 怪异和间歇性错误。不幸的是,您对 $argline 的使用依赖于分词来分隔多个参数;如果您使用的是 bash(不是通用的 POSIX shell),则可以改用数组;我会开始的。

  • 您到处都在使用相对文件路径,并且在脚本中 cding。这往往是脆弱和混乱的,因为文件路径在脚本的不同位置是不同的,并且用户传入的任何相对路径在脚本 cds 第一次在其他地方时将变得无效。更糟糕的是,当您 cd 时您没有检查错误,因此如果任何 cd 由于任何原因失败,那么整个脚本的其余部分将 运行 在错误的位置并奇怪地失败。你最好弄清楚系统的根目录在哪里(作为绝对路径),然后引用其中的所有内容(例如 < "$module_root/modules/$name/args.conf")。

  • 实际上,您并没有检查任何地方的错误。这通常是一个好主意,在编写任何类型的程序时,尝试考虑可能出错的地方以及您的程序应该如何响应(并且期望您没有想到的事情也会出错)。如果任何简单的命令失败,有些人喜欢使用 set -e 让他们的脚本退出,但是 this doesn't always do what you'd expect。我更喜欢在脚本中显式测试命令的退出状态,例如:

    command1 || {
        echo 'command1 failed!' >&2
        exit 1
    }
    if command2; then
        echo 'command2 succeeded!' >&2
    else
        echo 'command2 failed!' >&2
        exit 1
    fi
    
  • 您正在当前目录中创建临时文件,这有随机冲突的风险(同时与脚本的其他 运行s,任何文件恰好有您的名字'正在使用等)。最好在开始时创建一个临时目录,然后将所有内容存储在其中(同样,通过绝对路径):

    module_tmp="$(mktemp -dt module-system)" || {
        echo "Error creating temp directory" >&2
        exit 1
    }
    ...
    echo "$answer" >> "$module_tmp/arglist.tmp"
    

    (顺便说一句,请注意,我使用的是 $() 而不是反引号。它们更易于阅读,并且没有反引号所具有的一些微妙的句法古怪之处。我建议切换。)

  • 说到这里,您过度使用了临时文件;使用 shell 变量和内置 shell 功能可以很好地完成您正在做的很多事情。例如,不是从配置文件中读取行,然后将它们存储在临时文件中并使用 cut 将它们拆分为字段,您可以简单地从 echocut:

    arg="$(echo "$line" | cut -d ";" -f 1)"
    

    ...或者更好的是,使用 read 的内置功能根据 IFS 设置的任何内容拆分字段:

    while IFS=";" read -u3 arg requ description; do
    

    (请注意,由于对 IFS 的赋值是 read 命令的前缀,它只影响那个命令;全局更改 IFS 会产生奇怪的效果,并且应该尽可能避免。)

    类似地,将参数列表存储在一个文件中,将换行符转换为空格到另一个文件中,然后读取该文件...您可以跳过任何或所有这些步骤。如果您使用 bash,将 arg 列表存储在数组中:

    arglist=()
    while ...
        arglist+=("$answer") # or ("#arg=$answer")? Not sure of your syntax.
    done ...
    
    "$module_root/modules/$name/$interpreter" "$file" "${arglist[@]}"
    

    (这种带有双引号、大括号、方括号和 at 符号的混乱语法通常是 bash 中扩展数组的正确方法)。

    如果你不能指望像数组这样的 bash 扩展,你至少可以用一个简单的变量以旧的混乱方式来做:

    arglist=""
    while ...
        arglist="$arglist $answer" # or "$arglist $arg=$answer"? Not sure of your syntax.
    done ...
    
    "$module_root/modules/$name/$interpreter" "$file" $arglist
    

    ...但这 运行 参数被分词 and/or 扩展到文件列表的风险。