如何将对话框输入定向到变量?
我一直在教自己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 looongman 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变量result
和exitcode
的内容打印到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/null
。 exec
命令执行类似于整个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
在这种情况下,避免临时文件将是一个更好的解决方案,但是在许多情况下您无法避免临时文件。