変数に向けられたダイアログボックス入力を取得する方法は?


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」のように見えます。ファイルをcatしようとすると、エラーが発生します。そのため、-inputboxからユーザー入力を変数として取得することはできません。

サンプルスクリプト:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

ご覧のとおり、これは基本的なスクリプトです。変数を単語として取得することも可能dialog --inputboxですか?


私の経験では、2行目以降の空の行を削除すると、スクリプトは正常に機能します。または、mktempコマンドを使用して一時ファイルを作成することもできます。
ジャルノ

回答:


16

:DIは説明できません!!! あなたは彼らが言っているかを理解することができた場合は高度なバッシュ・スクリプトガイド:第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日)

要求に応じて、このスニペットが何をしているのかを行ごとに説明しようとします。

;行ごとに1つのコマンドを記述する場合、セミコロンは必要ないため、行末のすべてのセミコロンを省略することで簡略化することに注意してください。

I / O-ストリーム:

最初に、通信ストリームを理解する必要があります。0から9までの番号が付けられた10のストリームがあります。

  • ストリーム0(「STDIN」):
    「標準入力」、キーボードからデータを読み取るためのデフォルトの入力ストリーム。

  • ストリーム1(「STDOUT」):
    「標準出力」、ターミナルで通常のテキストを表示するために使用されるデフォルトの出力ストリーム。

  • ストリーム2(「STDERR」): 「標準エラー」、ターミナルで特別な目的のためにエラーまたは他のテキストを表示するために使用されるデフォルトの出力ストリーム。

  • ストリーム3〜9:
    自由に使用できる追加のストリーム。これらはデフォルトでは使用されず、何かが使用を試みるまで存在しません。

すべての「ストリーム」は、ファイル記述子によって内部的に表されていることに注意してください/dev/fd(これは、/proc/self/fdすべてのストリームに別のシンボリックリンクを含むシンボリックリンクです。少し複雑であり、動作にとって重要ではないため、ここで停止します)。標準ストリームには/dev/stdin/dev/stdoutおよびもあります/dev/stderr(これもシンボリックリンクなどです...)。

スクリプト:

  • exec 3>&1

    Bashビルトインをexec使用して、シェルにストリームリダイレクトを適用できます。つまり、以降のすべてのコマンドに影響します。詳細についてhelp execは、ターミナルで実行してください。

    この特殊なケースでは、ストリーム3はストリーム1(STDOUT)にリダイレクトされます。つまり、ストリーム3に送信したものはすべて、通常はSTDOUTに出力されるかのように端末に表示されます。

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

    この行は、多くの部分と構文構造で構成されています。

    • result=$(...)
      この構造体は、括弧内のコマンドを実行し、出力(STDOUT)をbash変数に割り当てますresult。で読み取り可能$resultです。このすべては、なんとなくveeeery looongで説明されていman bashます。

    • dialog --inputbox TEXT HEIGHT WIDTH
      このコマンドは、指定されたTEXT、テキスト入力フィールド、2つのボタンOKおよびCANCELを備えたTUIボックスを表示します。OKが選択されると、コマンドはステータス0で終了し、入力されたテキストをSTDERRに出力します。CANCELが選択されると、コード1で終了し、何も出力しません。詳細については、をご覧くださいman dialog

    • 2>&1 1>&3
      これらは2つのリダイレクトコマンドです。それらは右から左に解釈されます:

      1>&3 コマンドのストリーム1(STDOUT)をカスタムストリーム3にリダイレクトします。

      2>&1 その後、コマンドのストリーム2(STDERR)をストリーム1(STDOUT)にリダイレクトします。

      つまり、コマンドがSTDOUTに出力するものはすべてストリーム3に表示されるようになり、STDERRに表示されることを意図したものはすべてSTDOUTにリダイレクトされるようになります。

    そのため、行全体にテキストプロンプトが表示され(STDOUTでストリーム3にリダイレクトされ、シェルは最後に再びSTDOUTにリダイレクトされます- exec 3>&1コマンドを参照)、入力されたデータを割り当てます(STDERRで返され、STDOUTにリダイレクトされます) Bash変数にresult

  • exitcode=$?

    このコードは、dialog予約済みのBash変数$?(常に最後の終了コードを保持)を介して、以前に実行されたコマンドの終了コード(ここから)を取得し、単純に独自のBash変数に格納しますexitcode$exitcode再び読み通すことができます。詳細についてはで検索できますman bashが、しばらく時間がかかる場合があります...

  • exec 3>&-

    Bashビルトインをexec使用して、シェルにストリームリダイレクトを適用できます。つまり、以降のすべてのコマンドに影響します。詳細についてhelp execは、ターミナルで実行してください。

    この特殊なケースでは、ストリーム3は「ストリーム-」にリダイレクトされます。これは、単に閉じる必要があることを意味します。ストリーム3に送信されたデータは、今後どこにもリダイレクトされません。

  • echo $result $exitcode

    この単純なechoコマンド(詳細はman echo)ちょうど2つのバッシュ変数の内容印刷さresultexitcodeSTDOUTにします。ここには明示的または暗黙的なストリームリダイレクトがないため、それらは実際にはSTDOUTに表示されるため、単に端末に表示されます。なんて奇跡だ!;-)

概要:

まず、カスタムストリーム3に送信するすべてをSTDOUTにリダイレクトするようにシェルを設定し、ターミナルに表示されるようにします。

次に、dialogコマンドを実行し、元のSTDOUTをカスタムストリーム3にリダイレクトします。これは、最後に表示する必要があるためですが、一時的にSTDOUTストリームを他の何かに使用する必要があります。
その後、ダイアログウィンドウのユーザー入力が返されるコマンドの元のSTDERRをSTDOUTにリダイレクトします。
これで、STDOUT(STDERRからリダイレクトされたデータを保持する)をキャプチャして、変数に格納できます$result。必要なユーザー入力が含まれています!

また、dialogOKまたはCANCELがクリックされたかどうかを示すコマンドの終了コードも必要です。この値は予約済みのBash変数に示されており$?、独自の変数にコピーするだけです$exitcode

その後、ストリーム3を再度閉じます。必要がなくなったため、それ以上のリダイレクトを停止します。

最後に、通常、変数$result(ダイアログウィンドウのユーザー入力)と$exitcode(OKの場合は0、キャンセルの場合は1 )の両方の内容を端末に出力します。


使用execは不必要に複雑だと思います。なぜ私たちの出力を--stdout選択するdialogかリダイレクトするの2>&1 >/dev/ttyですか?
ジャーノ16

私の答えをご覧ください。
ジャーノ16

3
素晴らしい答えです!しかし、間違ったメモがあると思います-「右から左に解釈される」と言いますが、それは真実ではないと思います。bashのマニュアルからgnu.org/software/bash/manual/html_node/Redirections.htmlそれは彼らが発生したとしてリダイレクトが行われることを示している(つまりは、左から右へ)
ralfthewise

14

ダイアログ独自のツールの使用:--output-fdフラグ

ダイアログのmanページを読むと、オプション--output-fdがあり、デフォルトでSTDERRに行く代わりに、出力の行き先(STDOUT 1、STDERR 2)を明示的に設定できます。

以下に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の説明はどちらも、優れたソリューションとその概要を提供します。ただし、より詳細な分析は、なぜ機能するのを説明するのに有益であると考えています。

まず、2つのことを理解することが重要です:解決しようとしている問題は何か、対処しているシェルメカニズムの基本的な動作は何か。タスクは、コマンド置換を介してコマンドの出力をキャプチャすることです。誰もが知っている単純化された概要の下で、コマンド置換はコマンドをキャプチャし、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()使用します。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たとえば、リダイレクトを使用してコマンドを呼び出すと、シェルはそのstdoutファイル記述子を渡し、それを使用dup2()してそのファイル記述子をに書き込みます/dev/null。このexecコマンドは、シェルセッション全体でファイル記述子と同様の処理をdup2()実行するため、すべてのコマンドが既にリダイレクトされたファイル記述子を継承します。と同じexec 3>&1。ファイル記述子3は制御端末を参照/指すようになり、そのシェルセッションで実行されるコマンドはそれを認識します。

そのresult=$(dialog --inputbox test 0 0 2>&1 1>&3);ため、シェルは、発生するダイアログに書き込むためのパイプを作成しますが、2>&1最初にコマンドのファイル記述子2をそのパイプの書き込みファイル記述子に複製します(したがって、出力はパイプの最後と変数に読み込まれます) 、ファイル記述子1は3に複製されます。これにより、ファイル記述子1は制御端末を引き続き参照し、TUIダイアログが画面に表示されます。

さて、実際にはプロセスの現在の制御端末の省略形があり/dev/ttyます。したがって、ソリューションは、ファイル記述子を使用せずに、単純に次のように単純化できます。

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

覚えておくべき重要事項:

  • ファイル記述子は、各コマンドによってシェルから継承されます
  • コマンド置換はパイプとして実装されます
  • 重複したファイル記述子は元のファイル記述子と同じ場所を参照しますが、各ファイル記述子を個別に操作できます

こちらもご覧ください


また、マンページには、--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 "May fail"はあまり説得力のあるものではありませんが、これには例がありません。さらに、彼らはについて何も言及していませ--output-fd--stdout。これは私がここで使用したオプションであり、ではありません。次に、ダイアログが最初に標準出力に描画され、返される出力が2番目になります。これら2つのことを同時に行うことはありません。ただし、 --output-fd fd 1(STDOUT)を使用するために特に必要というわけではありません。別のファイル記述子に簡単にリダイレクトできます
Sergiy Kolodyazhnyy

多分それはどこでも動作するかもしれませんし、多分それはほとんどのシステムでのみ動作するでしょう。それは私のもので動作し、マンページには同様のオプションを注意して使用するように言われています。しかし、私がすでに言ったように、とにかく名前付きパイプには+1がふさわしい。
バイトコマンダー

ある程度の均衡を保つために、ここでコメントする必要があります。私にとって、これは唯一の直接的な答えかもしれません(1)同じツールのみを使用し、外部ツールなしでオプションを実装しました(2)Ubuntuで動作し、AUのすべてがそうです。:/悲しいことに、OPはこの質問を放棄しているようです。
user.dz

ここで通常のファイルの代わりに名前付きパイプを使用する利点は何ですか?使用後にパイプを削除しませんか?
ヤルノ

7

:DIは説明できません!!! 参照:Advanced Bash-Scripting Guide:Chapter 20. I / O Redirectionで彼らが言っていることを理解できるなら、新しい答えを書いてください

賞金が与えられました。説明については、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リダイレクト


そのオファーはまだ有効ですか?1年半前にあなたがそこで見つけたものを説明できると思います... :
バイトコマンダー

@ByteCommander、しかしあなたがそれを提供できるなら、私はあなたにそれを与えます、私は私の言葉にいます:D。
user.dz

@ByteCommander、投稿してください。
user.dz

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の与える-私は、これは別の方法を行うことができ非常にうれしいですが、それは私が求めていたものではないのです
Sergiy Kolodyazhnyy

@Sergあなたのコメントは無効ですが、私の答えはそうではありません:このユーティリティは、OPが要求するソリューションに完全に有効で簡単な代替手段を提供します。
Wtower

3

Sneetsherが提供する答えはややエレガントですが、何が間違っているのか説明でき$$ます。バックティック内では値が異なります(新しいシェルを開始し$$、現在のシェルの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

この場合、一時ファイルを回避する方が良い解決策になりますが、一時ファイルを回避できない状況は多くあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.