如何获取指向变量的对话框输入?

eme*_*oke 22 bash dialog

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

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

'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
Run Code Online (Sandbox Code Playgroud)

我看到它使用 2> 将 sdterr 发送到 /tmp/inputbox.tmp.$$,但输出文件看起来像“inputbox.tmp.21661”。当我尝试 cat 文件时,它给了我一个错误。所以我仍然无法从 --inputbox 获取用户输入作为变量。

示例脚本:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app
Run Code Online (Sandbox Code Playgroud)

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

Byt*_*der 19

:DI 无法解释!!!如果你能理解他们在Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection中所说的,写一个新的答案,我会给你50rep

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

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

^ 来自@Sneetsher 的回答(2014 年 7 月 4 日)

根据要求,我将尝试逐行解释此代码段的作用。

请注意,我将通过省略行尾的所有;分号来简化它,因为如果我们每行编写一个命令,它们就不是必需的。

I/O - 流:

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

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

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

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

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

请注意,所有“流”在内部都由文件描述符表示/dev/fd(这是一个符号链接,/proc/self/fd其中包含每个流的另一个符号链接......它有点复杂,对它们的行为并不重要,所以我在这里停止。)。标准流也有/dev/stdin,/dev/stdout/dev/stderr(它们又是符号链接,等等......)。

剧本:

  • exec 3>&1
    
    Run Code Online (Sandbox Code Playgroud)

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

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

  • result=$(dialog --inputbox test 0 0 2>&1 1>&3)
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    • dialog --inputbox TEXT HEIGHT WIDTH
      此命令显示一个带有给定文本的 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=$?
    
    Run Code Online (Sandbox Code Playgroud)

    此代码dialog通过保留的 Bash 变量$?(始终保存最后一个退出代码)检索先前执行的命令的退出代码(此处来自),并将其存储在我们自己的 Bash 变量中exitcode。可以再通读$exitcode一遍。您可以在 中搜索更多信息man bash,但这可能需要一段时间...

  • exec 3>&-
    
    Run Code Online (Sandbox Code Playgroud)

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

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

  • echo $result $exitcode
    
    Run Code Online (Sandbox Code Playgroud)

    这个简单的echo命令(更多信息man echo)只是打印这两个变量的Bash的内容result,并exitcode以标准输出。由于我们在这里不再有显式或隐式的流重定向,它们将真正出现在 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)输出到终端。

  • 很好的答案!但是,我相信您有一个不正确的注释-您说“它们将从右向左解释”,但我认为这不是真的。从 bash 手册 https://www.gnu.org/software/bash/manual/html_node/Redirections.html 它表明重定向在遇到时发生(即从左到右) (4认同)

Ser*_*nyy 16

使用对话框自己的工具:--output-fd 标志

如果您阅读对话框的手册页,则有 option --output-fd,它允许您明确设置输出的位置(STDOUT 1,STDERR 2),而不是默认情况下转到 STDERR。

波纹管您可以看到我正在运行示例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 
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

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

user.dz 的原始答案和ByteCommander 对此的解释都提供了一个很好的解决方案并概述了它的作用。不过,我相信更深入的分析可能是有益的,解释为什么它的工作原理。

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

在幕后,命令替换实际上是作为管道实现的,其中有一个子进程(运行的实际命令)和读取进程(将输出保存到变量)。通过简单的系统调用跟踪就可以看出这一点。请注意,文件描述符 3 是管道的读端,而 4 是写端。对于echo写入其stdout文件描述符 1的 的子进程,该文件描述符实际上是文件描述符 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,  <unfinished ...>
[pid  6200] dup2(4, 1)                  = 1
[pid  6200] write(1, "X\n", 2 <unfinished ...>
[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 +++
Run Code Online (Sandbox Code Playgroud)

让我们暂时回到最初的答案。由于现在我们知道dialog将 TUI 框写入stdout、应答到stderr并且在命令替换内 stdout通过管道传输到其他地方,因此我们已经有了解决方案的一部分 - 我们需要以stderr将通过管道传输到读取器进程的方式重新连接文件描述符。这是2>&1答案的一部分。但是,我们如何处理 TUI box 呢?

这就是文件描述符 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
Run Code Online (Sandbox Code Playgroud)

/dev/pts/5我当前的伪终端设备在哪里。因此,如果我们能以某种方式保存这个目的地,我们仍然可以将 TUI 框写入终端屏幕。这就是exec 3>&1它的作用。command > /dev/null例如,当您使用重定向调用命令时,shell 会传递它的 stdout 文件描述符,然后使用dup2()该文件描述符将该文件描述符写入/dev/null. 该exec命令为整个 shell 会话执行类似于dup2()文件描述符的操作,从而使任何命令都继承已重定向的文件描述符。与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"
Run Code Online (Sandbox Code Playgroud)

要记住的关键事项:

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

也可以看看


use*_*.dz 9

:DI 无法解释!!!如果你能看懂他们在参考中的意思:Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection,写一个新答案,我会给你50rep

赏金已给出,有关解释,请参阅ByteCommander 的回答。:) 这是历史的一部分。

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

来源: bash 中的对话框未正确抓取变量
参考:高级 Bash 脚本指南:第 20 章 I/O 重定向


jar*_*rno 6

这对我有用:

#!/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
Run Code Online (Sandbox Code Playgroud)

的手册页dialog讲述了 --stdout:

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

谁能告诉它在哪个平台或环境中不起作用?dialog那么重定向输出是否会2>&1 >/dev/tty更好?