如何将对话框输入定向到变量?

我一直在教自己bash脚本,并遇到了一个问题。 我编写了一个脚本来使用’read’命令从用户那里获取输入,并使该输入成为稍后在脚本中使用的变量。 该脚本有效,但….

我希望能够使用“对话框”进行设置。 我发现

‘dialog –inputbox’将输出定向到’stderr’,并且为了将该输入作为变量获取,您必须将其指向文件然后检索它。 我发现解释这个的代码是:

#!/bin/bash dialog --inputbox \ "What is your username?" 0 0 2> /tmp/inputbox.tmp.$$ retval=$? input=`cat /tmp/inputbox.tmp.$$` rm -f /tmp/inputbox.tmp.$$ case $retval in 0) echo "Your username is '$input'";; 1) echo "Cancel pressed.";; esac 

我看到它正在使用2>将sdterr发送到/tmp/inputbox.tmp.$$,但输出文件看起来像’inputbox.tmp.21661’。 当我尝试捕捉文件时,它给了我一个错误。 所以我仍然无法将–inputbox中的用户输入作为变量。

示例脚本:

 echo " What app would you like to remove? " read dead_app sudo apt-get remove --purge $dead_app 

所以你可以看到它是一个基本的脚本。 是否有可能将变量作为dialog --inputbox的单词?

:DI无法解释!!! 如果你能理解他们在Advanced Bash-Scripting Guide:第20章I / O重定向中所说的内容,请写一个新的答案,我会给你50rep

 exec 3>&1; result=$(dialog --inputbox test 0 0 2>&1 1>&3); exitcode=$?; exec 3>&-; echo $result $exitcode; 

参考: bash中的对话框没有正确获取变量

@答案来自@Sneetsher (2014年7月4日)

根据要求,我将尝试逐行解释这个片段的作用。

请注意,我将通过省略所有来简化它; 该行的分号结束,因为如果我们每行写一个命令就没有必要。

I / O – 流:

首先,您需要了解通信流。 有10个流,编号从0到9:

  • 流0(“STDIN”):
    “标准输入”,从键盘读取数据的默认输入流。

  • 流1(“STDOUT”):
    “标准输出”,用于在终端中显示正常文本的默认输出流。

  • 流2(“STDERR”): “标准错误”,默认输出流,用于在终端中显示特殊用途的错误或其他文本。

  • 流3-9:
    额外的,可自由使用的流。 它们在默认情况下不会被使用,并且在尝试使用它们之前不存在。

请注意,所有“流”都在内部由/dev/fd文件描述符表示(这是/proc/self/fd符号链接,它包含每个流的另一个符号链接……它有点复杂,对他们来说并不重要行为,所以我就此止步。) 标准流还有/dev/stdin/dev/stdout/dev/stderr (再次是符号链接等等)。

剧本:

  •  exec 3>&1 

    Bash内置的exec可用于将流重定向应用于shell,这意味着它会影响所有后续命令。 有关详细信息,请在终端中运行help exec

    在这种特殊情况下,流3被重定向到流1(STDOUT),这意味着我们稍后发送到流3的所有内容都将出现在我们的终端中,就像它通常打印到STDOUT一样。

  •  result=$(dialog --inputbox test 0 0 2>&1 1>&3) 

    这一行由许多部分和句法结构组成:

    • result=$(...)
      此结构在括号中执行命令,并将输出(STDOUT)分配给bash变量result 。 它可以通过$result读取。 所有这些都以某种方式在veloery looong man bash中进行了描述。

    • dialog --inputbox TEXT HEIGHT WIDTH
      此命令显示带有给定TEXT的TUI框,文本输入字段和两个按钮OK和CANCEL。 如果选择OK,则命令以状态0退出并将输入的文本输出到STDERR,如果选择了CANCEL,它将以代码1退出并且不打印任何内容。 有关更多信息,请阅读man dialog

    • 2>&1 1>&3
      这是两个重定向命令。 它们将从右到左解释:

      1>&3将命令的流1(STDOUT)重定向到自定义流3。

      2>&1将命令的流2(STDERR)重定向到流1(STDOUT)。

      这意味着命令打印到STDOUT的所有内容现在都显示在流3中,而打算在STDERR上显示的所有内容现在都会重定向到STDOUT。

    所以整行显示一个文本提示符(在STDOUT上,它被重定向到流3,shell再次重定向回到STDOUT – 参见exec 3>&1命令)并分配输入的数据(通过STDERR返回,然后重定向到STDOUT)到Bash变量result

  •  exitcode=$? 

    这段代码通过保留的Bash变量$?检索先前执行的命令的退出代码(这里是dialog$? (始终保留最后一个退出代码)并将其存储在我们自己的Bash变量exitcode 。 它可以再次通过$exitcode读取。 你可以在man bash搜索更多相关信息,但这可能需要一段时间……

  •  exec 3>&- 

    Bash内置的exec可用于将流重定向应用于shell,这意味着它会影响所有后续命令。 有关详细信息,请在终端中运行help exec

    在这种特殊情况下,流3被重定向到“stream – ”,这意味着它应该被关闭。 从现在开始,发送到流3的数据将不再被重定向到任何地方。

  •  echo $result $exitcode 

    这个简单的echo命令(关于man echo更多信息)只是将两个Bash变量resultexitcode的内容打印到STDOUT。 由于我们在这里没有明确或隐含的流重定向,它们将真正出现在STDOUT上,因此只是显示在终端中。 多么奇迹! 😉

摘要:

首先,我们将shell设置为将我们发送到自定义流3的所有内容重定向回STDOUT,以便它显示在我们的终端中。

然后我们运行dialog命令,将其原始STDOUT重定向到我们的自定义流3,因为它最终需要显示,但我们暂时需要使用STDOUT流来实现其他目的。
我们将命令的原始STDERR(后退对话窗口的用户输入)重定向到STDOUT。
现在我们可以捕获STDOUT(它保存来自STDERR的重定向数据)并将其存储在变量$result 。 它现在包含想要的用户输入!

我们还想要dialog命令的退出代码,它向我们显示是否单击了OK或CANCEL。 该值显示在保留的Bash变量$? 我们只是将它复制到我们自己的变量$exitcode

之后我们再次关闭流3,因为我们不再需要它,以阻止进一步的重定向。

最后,我们通常将两个变量$result (对话窗口的用户输入)和$exitcode (0表示OK,1表示CANCEL)的内容输出到终端。

使用对话框自带的工具: – output-fd flag

如果您阅读对话框的手册页,则有选项--output-fd ,它允许您显式设置输出的位置(STDOUT 1,STDERR 2),而不是默认设置为STDERR。

Bellow你可以看到我运行示例dialog命令,明确说明输出必须转到文件描述符1,这允许我将其保存到MYVAR中。

MYVAR=$(dialog --inputbox "THIS OUTPUT GOES TO FD 1" 25 25 --output-fd 1)

在此处输入图像描述

使用命名管道

具有很多隐藏潜力的替代方法是使用称为命名管道的东西。

 #!/bin/bash mkfifo /tmp/namedPipe1 # this creates named pipe, aka fifo # to make sure the shell doesn't hang, we run redirection # in background, because fifo waits for output to come out dialog --inputbox "This is an input box with named pipe" 40 40 2> /tmp/namedPipe1 & # release contents of pipe OUTPUT="$( cat /tmp/namedPipe1 )" echo "This is the output " $OUTPUT # clean up rm /tmp/namedPipe1 

在此处输入图像描述

使用备用方法更深入地概述user.dz的答案

user.dz和ByteCommander对这两者的解释都提供了一个很好的解决方案和概述。 但是,我认为更深入的分析可能有助于解释其工作原理。

首先,理解两件事很重要:我们试图解决的问题是什么,以及我们正在处理的shell机制的基本工作原理是什么。 任务是通过命令替换捕获命令的输出。 在每个人都知道的简单概述下,命令替换捕获命令的stdout并让它被其他东西重用。 在这种情况下, result=$(...)部分应该将由...指定的任何命令的输出保存到名为result的变量中。

在引擎盖下,命令替换实际上是作为管道实现的,其中有一个子进程(运行的实际命令)和读取进程(将输出保存到变量)。 通过简单的系统调用跟踪就可以看出这一点。 请注意,文件描述符3是管道的读取端,而4是写入端。 对于写入其stdout – 文件描述符1的echo的子进程,该文件描述符实际上是文件描述符4的副本,文件描述符4是管道的写入端。 请注意, stderr在这里没有扮演角色,只是因为它只是连接stdout的管道。

 $ strace -f -e pipe,dup2,write,read bash -c 'v=$(echo "X")' ... pipe([3, 4]) = 0 strace: Process 6200 attached [pid 6199] read(3,  [pid 6200] dup2(4, 1) = 1 [pid 6200] write(1, "X\n", 2  [pid 6199] <... read resumed> "X\n", 128) = 2 [pid 6200] <... write resumed> ) = 2 [pid 6199] read(3, "", 128) = 0 [pid 6200] +++ exited with 0 +++ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6200, si_uid=1000, si_status=0, si_utime=0, si_stime=0} --- +++ exited with 0 +++ 

让我们回到原来的答案一秒钟。 从现在开始我们知道dialog将TUI框写入stdout ,回答stderr ,并且在命令替换中stdout在其他地方被管道传输,我们已经有了解决方案的一部分 – 我们需要重新连接文件描述符,以便stderr将被管道传输到读者过程。 这是答案的2>&1部分。 但是,我们如何处理TUI盒?

这就是文件描述符3的用武之地dup2()系统调用允许我们复制文件描述符,使它们有效地引用同一个地方,但我们可以单独操作它们。 附加控制终端的进程的文件描述符实际上指向特定的终端设备。 如果你这样做,这很明显

 $ ls -l /proc/self/fd total 0 lrwx------ 1 user1 user1 64 Aug 20 10:30 0 -> /dev/pts/5 lrwx------ 1 user1 user1 64 Aug 20 10:30 1 -> /dev/pts/5 lrwx------ 1 user1 user1 64 Aug 20 10:30 2 -> /dev/pts/5 lr-x------ 1 user1 user1 64 Aug 20 10:30 3 -> /proc/6424/fd 

其中/dev/pts/5是我目前的伪终端设备。 因此,如果我们可以以某种方式保存此目的地,我们仍然可以将TUI框写入终端屏幕。 这就是exec 3>&1作用。 例如,当您使用重定向command > /dev/null调用命令时,shell会传递它的stdout文件描述符,然后使用dup2()将该文件描述符写入/dev/nullexec命令执行类似于整个shell会话的dup2()文件描述符的操作,从而使任何命令inheritance已经重定向的文件描述符。 与exec 3>&1 。 文件描述符3现在将引用/指向控制终端,并且在该shell会话中运行的任何命令都将知道它。

所以当result=$(dialog --inputbox test 0 0 2>&1 1>&3); 发生时,shell创建一个用于写入对话框的管道,但是2>&1将首先使命令的文件描述符2复制到该管道的写文件描述符上(从而使输出转到读取管道的末尾并进入变量),文件描述符1将被复制到3.这将使文件描述符1仍然引用控制终端,并且TUI对话框将显示在屏幕上。

现在,这个过程的当前控制终端实际上​​是一个简写,即/dev/tty 。 因此,可以简化解决方案而无需使用文件描述符,只需简化为:

 result=$(dialog --inputbox test 0 0 2>&1 1>/dev/tty); echo "$result" 

要记住的关键事项:

  • 每个命令都从shellinheritance文件描述符
  • 命令替换实现为管道
  • 重复的文件描述符将引用与原始文件描述符相同的位置,但我们可以分别操作每个文件描述符

也可以看看

  • 你什么时候使用额外的文件描述符?
  • bash下复制/关闭文件描述符中箭头符号的重要性

:DI无法解释!!! 如果您能够理解他们在参考文献中所说的内容: 高级Bash-Scripting Guide:第20章I / O重定向 ,请写一个新答案,我会给你50rep

给了赏金,解释见ByteCommander的回答 。 :)这是历史的一部分。

 exec 3>&1; result=$(dialog --inputbox test 0 0 2>&1 1>&3); exitcode=$?; exec 3>&-; echo $result $exitcode; 

来源: bash中的对话框没有正确抓取变量
参考: Advanced Bash-Scripting Guide:第20章I / O重定向

这对我有用:

 #!/bin/bash input=$(dialog --stdout --inputbox "What is your username?" 0 0) retval=$? case $retval in ${DIALOG_OK-0}) echo "Your username is '$input'.";; ${DIALOG_CANCEL-1}) echo "Cancel pressed.";; ${DIALOG_ESC-255}) echo "Esc pressed.";; ${DIALOG_ERROR-255}) echo "Dialog error";; *) echo "Unknown error $retval" esac 

dialog的手册页说明了–stout:

直接输出到标准输出。 提供此选项是为了与Xdialog兼容,但不建议在可移植脚本中使用它,因为curses通常会将其屏幕更新写入标准输出。 如果使用此选项,对话框将尝试重新打开终端,以便它可以写入显示器。 根据平台和环境,可能会失败。

任何人都可以告诉它在哪个平台或环境中不起作用? 将dialog输出重定向到2>&1 >/dev/tty改善吗?

如果其他人也从谷歌这里登陆,虽然这个问题专门针对bash,但这是另一种选择:

你可以使用zenity 。 Zenity是一个可以在bash脚本中使用的图形实用程序。 但当然这需要一个X服务器,正如user877329正确指出的那样。

 sudo apt-get install zenity 

然后在你的脚本中:

 RETVAL=`zenity --entry --title="Hi" --text="What is your username"` 

有用的链接 。

Sneetsher提供的答案有点优雅,但我可以解释错误:在反引号中, $$的值是不同的(因为它启动了一个新的shell,而$$是当前shell的PID)。 您需要将文件名放在变量中,然后在整个过程中引用该变量。

 #!/bin/bash t=$(mktemp -t inputbox.XXXXXXXXX) || exit trap 'rm -f "$t"' EXIT # remove temp file when done trap 'exit 127' HUP STOP TERM # remove if interrupted, too dialog --inputbox \ "What is your username?" 0 0 2>"$t" retval=$? input=$(cat "$t") # Prefer $(...) over `...` case $retval in 0) echo "Your username is '$input'";; 1) echo "Cancel pressed.";; esac 

在这种情况下,避免临时文件将是一个更好的解决方案,但是在许多情况下您无法避免临时文件。