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


18

我一直在自学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吗?


以我的经验,如果您删除第二行之后的空行,则脚本可以正常工作。或者,您可以使用mktempcommand创建一个临时文件。
jarno

Answers:


16

:DI无法解释!!!如果您能理解他们在Advanced Bash-Scripting Guide:第20章中说的话,请写一个新答案,我将给您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内置的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。这一切都在looong上用某种方式描述man bash

    • dialog --inputbox TEXT HEIGHT WIDTH
      此命令显示一个带有给定TEXT的TUI框,一个文本输入字段以及两个按钮OK和CANCEL。如果选择“确定”,则该命令以状态0退出,并将输入的文本打印到STDERR,如果选择了“取消”,它将以代码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内置的Bash exec可用于将流重定向应用到Shell,这意味着它会影响以下所有命令。有关更多信息,请help exec在您的终端中运行。

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

  • echo $result $exitcode

    这个简单的echo命令(更多信息man echo)只是打印这两个变量的Bash的内容result,并exitcode以标准输出。由于这里不再有显式或隐式流重定向,因此它们将真正出现在STDOUT上,因此仅显示在终端中。真是奇迹!;-)

摘要:

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

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

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

之后,由于不再需要流3,因此将其停止,以停止对其进行进一步的重定向。

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


我认为使用exec不必要地复杂。为什么不只是让我们--stdout选择dialog或重定向输出2>&1 >/dev/tty呢?
jarno

请看我的回答
jarno

3
好答案!但是,我相信您有一个不正确的注释-您说“它们将从右向左解释”,但我认为那是不正确的。从bash手册gnu.org/software/bash/manual/html_node/Redirections.html可以看出,重定向是在遇到重定向时发生的(即,从左到右)
ralfthewise

14

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

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

在下面,您可以看到我正在运行sample 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的副本,该文件描述符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 +++

让我们再回到原始答案。从现在起,我们知道dialog将TUI框写入stdout,回答stderr,并且在命令替换 中将其传递stdout到其他位置,我们已经有了解决方案的一部分-我们需要以stderr将其传递给阅读器进程的方式重新连接文件描述符。这就是2>&1答案的一部分。但是,我们如何使用TUI box?

那就是文件描述符3出现的地方dup2()。syscall允许我们复制文件描述符,使它们有效地指向同一位置,但我们可以单独操作它们。具有控制终端的进程的文件描述符实际上指向特定的终端设备。这很明显,如果你这样做

$ 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命令在整个Shell会话中执行类似于dup2()文件描述符的操作,因此使任何命令都继承已重定向的文件描述符。与相同exec 3>&13现在,文件描述符将引用/指向控制终端,并且在该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继承文件描述符
  • 命令替换被实现为管道
  • 复制的文件描述符将与原始文件描述符指向相同的位置,但是我们可以分别操纵每个文件描述符

也可以看看


该联机帮助页还说,该--stdout选项可能很危险,并且在某些系统上很容易失败,我认为--output-fd 1这样做是相同的:--stdout: Direct output to the standard output. This option is provided for compatibility with Xdialog, however using it in portable scripts is not recommended, since curses normally writes its screen updates to the standard output. If you use this option, dialog attempts to reopen the terminal so it can write to the display. Depending on the platform and your environment, that may fail.-但是,命名管道的想法很酷!
字节指挥官

@ByteCommander“可能失败”并不是很令人信服,因为这里没有提供示例。另外,他们什么也没有提及--output-fd,这是我在这里使用的选项,不是--stdout。其次,首先在stdout上绘制对话框,然后返回返回的输出。我们不能同时做这两件事。但是, --output-fd 并不特别要求使用fd 1(STDOUT)。可以轻松地将其重定向到另一个文件描述符
Sergiy Kolodyazhnyy 2015年

我不确定,也许它在任何地方都可以使用,也许只在大多数系统上都可以使用。它可以在我的系统上运行,并且联机帮助页上说,我肯定会知道谨慎使用类似选项。但是正如我已经说过的,无论如何,对于命名管道+1应该是应得的。
字节指挥官

我要在这里发表评论,以保持平衡。对我而言,这可能是唯一直接的规范答案(1)它仅使用相同的工具并实现了选项,而没有任何外部工具(2)它确实在Ubuntu中工作,并且所有有关AU的内容。:/可悲的是,OP似乎放弃了这个问题。
user.dz 2015年

在这里使用命名管道而不是常规文件有什么优势?您不想在使用后删除管道吗?
jarno

7

:DI无法解释!!!如果您能理解他们在参考资料中所说的话:高级Bash脚本指南:第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中的对话框不能正确地获取变量
参考:高级Bash脚本指南:第20章I / O重定向


那个提议仍然有效吗?我想我可以解释您在一年半前发现的东西... :-)
字节指挥官

@ByteCommander,但是,如果您能提供,我会告诉您,我会按您的意愿:D。
user.dz 2015年

@ByteCommander,请在发布后对我执行ping操作。
user.dz 2015年

1
完蛋了!askubuntu.com/a/704616/367990希望您了解所有内容并喜欢“尤里卡”!时刻。:-D如果有任何不清楚的地方,请发表评论。
字节指挥官

4

这对我有用:

#!/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介绍--stdout:

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

谁能说出它在哪个平台或环境下不起作用?那么将dialog输出重定向到2>&1 >/dev/tty更好地工作吗?


4

如果其他人也从Google登陆,尽管此问题专门针对bash,这是另一种选择:

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

sudo apt-get install zenity

然后在您的脚本中:

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

有用的链接


3
除非有没有X服务器
user877329

1
OP想要了解dialog。就像我来问你“如何用python写这个和那个?”,但是你给我
bash-

@Serg您的评论无效,我的回答不是:该实用程序提供了OP所要求的解决方案的完美有效且简单的替代方案。
Wtower

3

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.