Bagaimana cara mendapatkan input kotak dialog diarahkan ke variabel?

18

Saya telah belajar sendiri tentang bash scripting dan mengalami masalah. Saya telah menulis skrip untuk mengambil input dari pengguna, menggunakan perintah 'baca', dan menjadikan input tersebut variabel untuk digunakan nanti dalam skrip. Script berfungsi, tetapi ....

Saya ingin dapat mengaturnya menggunakan 'dialog'. Saya menemukan itu

'dialog --inputbox' akan mengarahkan output ke 'stderr' dan untuk mendapatkan input itu sebagai variabel, Anda harus mengarahkannya ke file dan kemudian mengambilnya. Kode yang saya temukan untuk menjelaskan ini adalah:

#!/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

Saya melihat bahwa ia mengirim sdterr ke /tmp/inputbox.tmp.$$ dengan 2>, tetapi file outputnya terlihat seperti 'inputbox.tmp.21661'. Ketika saya mencoba dan menyimpan file itu memberi saya kesalahan. Jadi saya masih tidak bisa mendapatkan input pengguna dari --inputbox sebagai variabel.

Contoh skrip:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Jadi seperti yang Anda lihat itu adalah skrip dasar. Apakah mungkin untuk mendapatkan variabel sebagai kata dari dialog --inputbox?

emerikanbloke
sumber
Dalam pengalaman saya, skrip berfungsi dengan baik, jika Anda menghapus baris kosong setelah baris ke-2. Atau, Anda bisa menggunakan mktempperintah untuk membuat file sementara.
jarno

Jawaban:

16

: AKU tidak bisa menjelaskannya !!! Jika Anda dapat memahami apa yang mereka katakan di Panduan Bash-Scripting Lanjutan: Bab 20. I / O Redirection , tulis jawaban baru dan saya akan memberi Anda 50rep :

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

Referensi: Dialog di bash tidak mengambil variabel dengan benar

^ jawaban dari @Sneetsher (4 Jul 2014)

Seperti yang diminta, saya akan mencoba menjelaskan apa yang dilakukan cuplikan ini baris demi baris.

Perhatikan bahwa saya akan menyederhanakannya dengan menghilangkan semua ;titik koma di baris yang berakhir, karena mereka tidak perlu jika kita menulis satu perintah per baris.

I / O - Streaming:

Pertama, Anda perlu memahami aliran komunikasi. Ada 10 aliran, diberi nomor dari 0 hingga 9:

  • Streaming 0 ("STDIN"):
    "Input standar", aliran input default untuk membaca data dari keyboard.

  • Streaming 1 ("STDOUT"):
    "Output standar", aliran output default yang digunakan untuk menampilkan teks normal di terminal.

  • Stream 2 ("STDERR"): "Standard error", aliran output default yang digunakan untuk menampilkan kesalahan atau teks lain untuk keperluan khusus di terminal.

  • Streaming 3-9: Streaming
    tambahan, yang dapat digunakan secara bebas. Mereka tidak digunakan secara default dan tidak ada sampai ada sesuatu yang mencoba menggunakannya.

Perhatikan bahwa semua "aliran" diwakili secara internal oleh deskriptor file di /dev/fd(yang merupakan tautan simbolis /proc/self/fdyang berisi tautan simbolis lain untuk setiap aliran ... ini sedikit rumit dan tidak penting untuk perilaku mereka, jadi saya berhenti di sini.). Streaming standar juga ada /dev/stdin, /dev/stdoutdan /dev/stderr(yang merupakan tautan simbolis lagi, dll ...).

Naskah:

  • exec 3>&1

    Bash built-in execdapat digunakan untuk menerapkan pengalihan aliran ke shell, yang berarti mempengaruhi semua perintah berikut. Untuk info lebih lanjut, jalankan help execdi terminal Anda.

    Dalam kasus khusus ini, stream 3 dialihkan ke stream 1 (STDOUT), itu berarti semua yang kami kirim ke stream 3 nanti akan muncul di terminal kami seolah-olah itu biasanya dicetak ke STDOUT.

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

    Baris ini terdiri dari banyak bagian dan struktur sintaksis:

    • result=$(...)
      Struktur ini mengeksekusi perintah dalam tanda kurung dan menetapkan output (STDOUT) ke variabel bash result. Dapat dibaca melalui $result. Semua ini dijelaskan entah bagaimana di dapur keras man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Perintah ini menampilkan kotak TUI dengan TEXT yang diberikan, bidang input teks dan dua tombol OK dan BATAL. Jika OK dipilih, perintah keluar dengan status 0 dan mencetak teks yang dimasukkan ke STDERR, jika BATAL dipilih, itu akan keluar dengan kode 1 dan tidak mencetak apa pun. Untuk info lebih lanjut, baca man dialog.

    • 2>&1 1>&3
      Ini adalah dua perintah pengalihan. Mereka akan ditafsirkan dari kanan ke kiri:

      1>&3 mengarahkan aliran perintah 1 (STDOUT) ke aliran khusus 3.

      2>&1 pengalihan setelah itu aliran perintah 2 (STDERR) untuk streaming 1 (STDOUT).

      Itu berarti bahwa semua perintah yang dicetak ke STDOUT sekarang muncul di stream 3, sementara semua yang dimaksudkan untuk muncul di STDERR sekarang akan dialihkan ke STDOUT.

    Jadi seluruh baris menampilkan prompt teks (pada STDOUT, yang dialihkan ke streaming 3, yang mana shell kembali kembali ke STDOUT pada akhirnya - lihat exec 3>&1perintah) dan memberikan data yang dimasukkan (dikembalikan melalui STDERR, kemudian diarahkan ke STDOUT) ke variabel Bash result.

  • exitcode=$?

    Kode ini mengambil kode keluar perintah yang sebelumnya dieksekusi (di sini dari dialog) melalui variabel Bash yang dipesan $?(selalu memegang kode keluar terakhir) dan hanya menyimpannya dalam variabel Bash kita sendiri exitcode. Itu bisa dibaca $exitcodelagi. Anda dapat mencari lebih banyak info tentang ini di man bash, tetapi itu mungkin perlu waktu ...

  • exec 3>&-

    Bash built-in execdapat digunakan untuk menerapkan pengalihan aliran ke shell, yang berarti mempengaruhi semua perintah berikut. Untuk info lebih lanjut, jalankan help execdi terminal Anda.

    Dalam kasus khusus ini, aliran 3 dialihkan ke "aliran -", yang artinya harus ditutup. Data yang dikirim ke streaming 3 tidak akan dialihkan ke mana pun lagi mulai sekarang.

  • echo $result $exitcode

    echoPerintah sederhana ini (info lebih lanjut tentang man echo) hanya mencetak konten dari dua variabel Bash resultdan exitcodeke STDOUT. Karena kami tidak memiliki pengalihan aliran eksplisit atau implisit di sini lagi, mereka akan benar-benar muncul di STDOUT dan karenanya ditampilkan di terminal. Sungguh keajaiban! ;-)

Ringkasan:

Pertama, kami mengatur shell untuk mengarahkan kembali semua yang kami kirim ke custom stream 3 kembali ke STDOUT, sehingga muncul di terminal kami.

Kemudian kita menjalankan dialogperintah, mengarahkan STDOUT aslinya ke aliran kustom 3 kita, karena itu perlu ditampilkan pada akhirnya, tetapi untuk sementara kita perlu menggunakan aliran STDOUT untuk hal lain.
Kami mengarahkan STDERR asli dari perintah, di mana input pengguna jendela dialog dikembalikan, ke STDOUT sesudahnya.
Sekarang kita dapat menangkap STDOUT (yang menyimpan data yang diarahkan dari STDERR) dan menyimpannya dalam variabel kita $result. Ini berisi input pengguna yang diinginkan sekarang!

Kami juga menginginkan dialogkode keluar perintah, yang menunjukkan kepada kami apakah OK atau BATAL diklik. Nilai ini disajikan dalam variabel Bash yang dipesan $?dan kami hanya menyalinnya ke variabel kami sendiri $exitcode.

Setelah itu kami menutup streaming 3 lagi, karena kami tidak membutuhkannya lagi, untuk menghentikan pengalihan lebih lanjut.

Akhirnya, kami biasanya menampilkan konten kedua variabel $result(input pengguna dari jendela dialog) dan $exitcode(0 untuk OK, 1 untuk BATAL) ke terminal.

Komandan Byte
sumber
Saya pikir menggunakan execitu tidak perlu rumit. Mengapa tidak hanya kita --stdoutopsi dialogatau mengarahkan outputnya 2>&1 >/dev/tty?
jarno
Tolong lihat jawaban saya .
jarno
3
Jawaban bagus! Namun, saya yakin Anda memiliki satu catatan yang salah - Anda mengatakan bahwa "Mereka akan ditafsirkan dari kanan ke kiri" tetapi saya percaya itu tidak benar. Dari manual bash, gnu.org/software/bash/manual/html_node/Redirections.html, ini menunjukkan bahwa pengalihan berlangsung ketika ditemui (mis. Kiri ke kanan)
bahkan
14

Menggunakan alat dialog sendiri: --output-fd flag

Jika Anda membaca halaman manual untuk dialog, ada opsi --output-fd, yang memungkinkan Anda untuk secara eksplisit mengatur di mana output berjalan (STDOUT 1, STDERR 2), alih-alih secara default pergi ke STDERR.

Di bawah ini Anda dapat melihat saya menjalankan dialogperintah sampel , dengan secara eksplisit menyatakan bahwa output harus masuk ke file descriptor 1, yang memungkinkan saya untuk menyimpannya ke MYVAR.

MYVAR=$(dialog --inputbox "THIS OUTPUT GOES TO FD 1" 25 25 --output-fd 1)

masukkan deskripsi gambar di sini

Menggunakan pipa bernama

Pendekatan alternatif yang memiliki banyak potensi tersembunyi, adalah dengan menggunakan sesuatu yang dikenal sebagai pipa bernama .

#!/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 

masukkan deskripsi gambar di sini

Tinjauan yang lebih mendalam tentang jawaban user.dz dengan pendekatan alternatif

Jawaban asli oleh user.dz dan penjelasan ByteCommander tentang keduanya memberikan solusi dan ikhtisar yang baik tentang apa yang dilakukannya. Namun, saya percaya analisis yang lebih dalam bisa bermanfaat untuk menjelaskan mengapa itu berhasil.

Pertama-tama, penting untuk memahami dua hal: apa masalah yang kita coba selesaikan dan apa yang menjadi dasar mekanisme shell yang kita hadapi. Tugasnya adalah untuk menangkap output dari suatu perintah melalui substitusi perintah. Di bawah gambaran sederhana yang diketahui semua orang, pergantian perintah menangkap stdoutperintah dan membiarkannya digunakan kembali oleh sesuatu yang lain. Dalam hal ini, result=$(...)bagian harus menyimpan output dari perintah apa pun yang ditunjuk oleh ...menjadi variabel yang dipanggil result.

Di bawah kap, substitusi perintah sebenarnya diimplementasikan sebagai pipa, di mana ada proses anak (perintah aktual yang berjalan) dan proses membaca (yang menyimpan output ke variabel). Ini terbukti dengan jejak sederhana dari panggilan sistem. Perhatikan bahwa file deskriptor 3 adalah ujung baca dari pipa, sedangkan 4 adalah akhir tulis. Untuk proses anak echo, yang menulis ke stdout- file deskriptor 1, file deskriptor tersebut sebenarnya adalah salinan file deskriptor 4, yang merupakan akhir penulisan pipa. Perhatikan bahwa stderrini tidak berperan di sini, hanya karena itu hanya pipa penghubung 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 +++

Mari kita kembali ke jawaban semula. Karena sekarang kita tahu bahwa dialogmenulis kotak TUI ke stdout, menjawab stderr, dan dalam substitusi perintah stdoutdisalurkan ke tempat lain, kita sudah memiliki bagian dari solusi - kita perlu me-rewire deskriptor file sedemikian rupa yang stderrakan disalurkan ke proses pembaca. Ini adalah2>&1 bagian dari jawabannya. Namun, apa yang kita lakukan dengan kotak TUI?

Di situlah file deskriptor 3 masuk. dup2()Syscall memungkinkan kita untuk menduplikasi deskriptor file, membuatnya secara efektif merujuk ke tempat yang sama, namun kita dapat memanipulasinya secara terpisah. File deskriptor dari proses yang memiliki terminal kontrol terpasang sebenarnya menunjuk ke perangkat terminal tertentu. Ini terbukti jika Anda melakukannya

$ 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

di mana /dev/pts/5perangkat pseudo-terminal saya saat ini. Jadi, jika kita dapat menyimpan tujuan ini, kita masih dapat menulis kotak TUI ke layar terminal. Itu yang exec 3>&1dilakukannya. Ketika Anda memanggil perintah dengan pengalihan command > /dev/nullmisalnya, shell melewati itu deskriptor file stdout dan kemudian menggunakan dup2()untuk menulis file deskriptor itu /dev/null. The execPerintah Melakukan sesuatu yang mirip dengandup2() file descriptor untuk sesi shell utuh, sehingga membuat perintah mewarisi sudah diarahkan file descriptor. Sama dengan exec 3>&1. Deskriptor file 3sekarang akan merujuk ke / menunjuk ke terminal pengendali, dan perintah apa pun yang berjalan di sesi shell akan mengetahuinya.

Jadi ketika result=$(dialog --inputbox test 0 0 2>&1 1>&3);terjadi, shell membuat pipa untuk dialog untuk menulis, tetapi juga2>&1 pertama-tama akan membuat file descriptor 2 perintah diduplikasi ke deskriptor file tulis dari pipa itu (sehingga membuat output pergi ke ujung baca pipa dan ke dalam variabel) , sementara file deskriptor 1 akan digandakan ke 3. Ini akan membuat file deskriptor 1 masih merujuk ke terminal pengendali, dan dialog TUI akan muncul di layar.

Sekarang, sebenarnya ada jalan pintas untuk terminal pengontrol saat ini dari proses, yaitu /dev/tty. Dengan demikian, solusinya dapat disederhanakan tanpa menggunakan deskriptor file, cukup menjadi:

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

Hal-hal penting untuk diingat:

  • deskriptor file diwarisi dari shell oleh setiap perintah
  • substitusi perintah diimplementasikan sebagai pipa
  • deskriptor file yang digandakan akan merujuk ke tempat yang sama dengan yang asli, tetapi kami dapat memanipulasi setiap deskriptor file secara terpisah

Lihat juga

Sergiy Kolodyazhnyy
sumber
Halaman manual juga mengatakan bahwa --stdoutopsi bisa berbahaya dan dengan mudah gagal pada beberapa sistem, dan saya pikir --output-fd 1melakukan hal yang sama: --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.- Namun, ide pipa bernama itu keren!
Byte Commander
@ByteCommander "Mungkin gagal" tidak terlalu meyakinkan, karena ini tidak memberikan contoh. Selain itu, mereka tidak menyebutkan apa pun tentang --output-fd, yang merupakan opsi yang saya gunakan di sini, tidak --stdout. Kedua, dialog sedang digambar pada stdout pertama, output yang dikembalikan adalah kedua. Kami tidak melakukan dua hal ini secara bersamaan. Namun, --output-fd tidak secara khusus mengharuskan seseorang untuk menggunakan fd 1 (STDOUT). Itu dapat dengan mudah dialihkan ke deskriptor file lain
Sergiy Kolodyazhnyy
Saya tidak yakin, mungkin ini berfungsi di mana-mana, mungkin hanya bekerja di sebagian besar sistem. Ini bekerja pada saya dan halaman manual mengatakan untuk menggunakan opsi serupa dengan hati-hati yang saya tahu pasti. Tapi seperti yang sudah saya katakan, +1 layak untuk pipa bernama pula.
Byte Commander
Saya harus berkomentar di sini, untuk menjaga keseimbangan. Bagi saya, ini mungkin satu-satunya jawaban kanonik langsung (1) hanya menggunakan alat yang sama & menerapkan opsi tanpa alat eksternal (2) Ini berfungsi di Ubuntu dan bahwa semua tentang AU. : / sayangnya OP tampaknya mengabaikan pertanyaan ini.
user.dz
Apa keuntungan menggunakan pipa bernama daripada file biasa di sini? Apakah Anda tidak ingin menghapus pipa setelah digunakan?
jarno
7

: AKU tidak bisa menjelaskannya !!! Jika Anda dapat memahami apa yang mereka katakan dalam referensi: Panduan Bash-Scripting Lanjutan: Bab 20. I / O Redirection , tulis jawaban baru dan saya akan memberi Anda 50rep

Bounty diberikan, untuk penjelasan, lihat jawaban ByteCommander . :) Ini adalah bagian dari sejarah.

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

Sumber: Dialog di bash tidak mengambil variabel dengan benar
Referensi: Panduan Script Bash Lanjutan: Bab 20. Pengarahan I / O

user.dz
sumber
apakah penawaran itu masih berlaku? Saya pikir saya bisa menjelaskan apa yang Anda temukan di sana satu setengah tahun yang lalu ... :-)
Byte Commander
@ByteCommander, tetapi jika Anda dapat memberikan itu, saya akan memberikan Anda, saya akan kata-kata saya: D.
user.dz
@ByteCommander, tolong, ping saya setelah Anda mempostingnya.
user.dz
1
Jadi! askubuntu.com/a/704616/367990 Saya harap Anda memahami segalanya dan menikmati "Eureka!" saat. :-D Tinggalkan komentar jika ada yang tidak jelas.
Byte Commander
4

Ini bekerja untuk saya:

#!/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

Halaman manual dialogmenceritakan tentang --stdout:

Output langsung ke output standar. Opsi ini disediakan untuk kompatibilitas dengan Xdialog, namun menggunakannya dalam skrip portabel tidak dianjurkan, karena kutukan biasanya menulis pembaruan layarnya ke output standar. Jika Anda menggunakan opsi ini, dialog akan mencoba untuk membuka kembali terminal sehingga dapat menulis ke layar. Bergantung pada platform dan lingkungan Anda, itu mungkin gagal.

Adakah yang tahu di platform atau lingkungan mana itu tidak berfungsi? Apakah mengarahkan ulang dialogoutput 2>&1 >/dev/ttysebagai gantinya bekerja lebih baik?

jarno
sumber
4

Jika ada orang lain yang mendarat di sini dari Google, dan meskipun pertanyaan ini meminta bash secara khusus, berikut adalah alternatif lain:

Anda dapat menggunakan zenity . Zenity adalah utilitas grafis yang dapat digunakan di dalam skrip bash. Tapi tentu saja ini akan membutuhkan server X seperti yang ditunjukkan oleh user877329.

sudo apt-get install zenity

Kemudian dalam skrip Anda:

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

Tautan yang bermanfaat .

Wtower
sumber
3
Kecuali jika tidak ada server X
user877329
1
OP ingin tahu tentang dialog. Ini seperti saya datang dan bertanya kepada Anda, "Bagaimana saya menulis ini dan itu dengan python?", Tetapi Anda memberi saya bash - saya sangat senang ini bisa dilakukan dengan cara yang berbeda, tapi bukan itu yang saya tanyakan
Sergiy Kolodyazhnyy
@Serg komentar Anda tidak valid, jawaban saya tidak: Utilitas memberikan alternatif yang benar-benar valid dan sederhana untuk solusi yang diminta oleh OP.
Wtower
3

Jawaban yang diberikan oleh Sneetsher agak lebih elegan, tetapi saya dapat menjelaskan apa yang salah: Nilai $$berbeda di dalam backticks (karena itu memulai shell baru, dan $$merupakan PID dari shell saat ini). Anda akan ingin memasukkan nama file dalam suatu variabel, kemudian merujuk ke variabel itu di seluruh.

#!/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

Dalam hal ini, menghindari file sementara akan menjadi solusi yang lebih baik, tetapi akan ada banyak situasi di mana Anda tidak dapat menghindari file temp.

tripleee
sumber