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

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

我一直在自学 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”。当我尝试 cat 该文件时,它给出了一个错误。所以我仍然无法从 --inputbox 获取用户输入作为变量。

示例脚本:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

如您所见,这是一个基本脚本。是否可以从中获取变量作为单词dialog --inputbox

答案1

:我没法解释!如果你能理解他们在说什么高级 Bash 脚本指南:第 20 章 I/O 重定向,写一个新的答案,我会给你50次

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。所有这些都以某种方式在非常长的时间内描述man bash

    • dialog --inputbox TEXT HEIGHT WIDTH
      此命令显示一个 TUI 框,其中包含给定的 TEXT、文本输入字段和两个按钮 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=$?
    

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

  • exec 3>&-
    

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

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

  • echo $result $exitcode
    

    这个简单的echo命令(更多信息见)只是将两个 Bash 变量和man echo的内容打印到 STDOUT。由于这里不再有显式或隐式的流重定向,它们实际上会出现在 STDOUT 上,因此只会显示在终端上。真是个奇迹!;-)resultexitcode

概括:

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

然后我们运行dialog命令,将其原始 STDOUT 重定向到我们的自定义流 3,因为它最终需要显示,但我们暂时需要将 STDOUT 流用于其他用途。
之后,我们将命令的原始 STDERR(对话窗口的用户输入返回的位置)重定向到 STDOUT。
现在我们可以捕获 STDOUT(其中包含从 STDERR 重定向的数据)并将其存储在我们的变量中$result。它现在包含所需的用户输入!

我们还需要dialog命令的退出代码,它告诉我们是单击了“确定”还是“取消”。此值显示在保留的 Bash 变量中$?,我们只需将其复制到我们自己的变量中即可$exitcode

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

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

答案2

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

如果您阅读对话框的手册页,就会发现有一个选项--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 

在此处输入图片描述

更深入的概述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 +++

让我们回到最初的答案。既然现在我们知道将dialogTUI 框写入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/null。该exec命令执行类似于dup2()整个 shell 会话的文件描述符,从而使任何命令都继承已重定向的文件描述符。 也一样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"

要记住的关键事项:

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

也可以看看

答案3

:D我解释不了!!!如果你能理解他们在参考文献中说了什么:高级 Bash 脚本指南:第 20 章 I/O 重定向,写一个新的答案,我会给你50次

悬赏已发放,详情请参阅ByteCommander 的回答。:)这是历史的一部分。

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

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

答案4

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

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

相关内容