Bir değişkene yönelik iletişim kutusu girdisi nasıl alınır?


18

Kendime bash komut dosyası yazmayı öğretiyorum ve bir sorunla karşılaştım. 'Okuma' komutunu kullanarak kullanıcıdan girdi almak için bir komut dosyası yazdım ve daha sonra komut dosyasında kullanmak için bir değişken olun. Senaryo çalışıyor, ama ....

'Diyalog'u kullanarak kurulum yapabilmek istiyorum. Bunu öğrendim

'dialog --inputbox' çıktıyı 'stderr' e yönlendirir ve bu girdiyi değişken olarak almak için onu bir dosyaya yönlendirmeniz ve sonra geri almanız gerekir. Bunu açıklamak için bulduğum kod:

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

Ben sdterr 2> ile /tmp/inputbox.tmp.$$'e gönderdiğini görüyorum, ancak çıktı dosyası 'inputbox.tmp.21661' gibi görünüyor. Dosyayı denediğimde bana bir hata veriyor. Yani hala bir değişken olarak --inputbox kullanıcı girişi alamıyorum.

Örnek Komut Dosyası:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Gördüğünüz gibi temel bir betik. Değişkeni bir kelime olarak almak bile mümkün müdür dialog --inputbox?


Benim deneyimime göre, 2. satırdan sonra boş satırı kaldırırsanız, komut dosyası iyi çalışır. Alternatif olarak, mktempgeçici bir dosya oluşturmak için command komutunu kullanabilirsiniz .
jarno

Yanıtlar:


16

: DI açıklayamıyor !!! Ne söylediklerini Gelişmiş Bash-Scripting Kılavuzu'nda anlayabiliyorsanız : Bölüm 20. G / Ç Yönlendirme , yeni bir cevap yazın, size 50rep vereceğim :

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

Referans: bash iletişim kutusu değişkenleri doğru şekilde yakalamıyor

^ @Sneetsher kullanıcısının yanıtı (4 Tem 2014)

İstendiği gibi, bu snippet'in satır satır ne yaptığını açıklamaya çalışacağım.

;Satır uçlarındaki tüm noktalı virgülleri atlayarak basitleştireceğimi unutmayın , çünkü her satıra bir komut yazarsak gerekli değildir.

G / Ç - Akışlar:

İlk olarak, iletişim akışlarını anlamanız gerekir. 0'dan 9'a kadar numaralandırılmış 10 akış vardır:

  • Akış 0 ("STDIN"):
    "Standart giriş", klavyeden veri okumak için varsayılan giriş akışı.

  • Akış 1 ("STDOUT"):
    "Standart çıkış", terminalde normal metni göstermek için kullanılan varsayılan çıkış akışı.

  • Akış 2 ("STDERR"): "Standart hata", terminaldeki hataları veya diğer metinleri görüntülemek için kullanılan varsayılan çıkış akışı.

  • Akış 3-9:
    Ek, serbestçe kullanılabilir akışlar. Varsayılan olarak kullanılmazlar ve bir şey onları kullanmaya çalışana kadar mevcut değildirler.

Tüm "akışların" dahili olarak dosya tanımlayıcıları tarafından temsil edildiğine dikkat edin /dev/fd( /proc/self/fdher akış için başka bir sembolik bağlantı içeren sembolik bir bağlantıdır ... biraz karmaşıktır ve davranışları için önemli değildir, bu yüzden burada duruyorum.). Standart akışları da var /dev/stdin, /dev/stdoutve /dev/stderr(sembolik linkler ... vb, yine olan).

Senaryo:

  • exec 3>&1

    Dahili Bash execkabuğa bir akış yeniden yönlendirmesi uygulamak için kullanılabilir, yani aşağıdaki tüm komutları etkiler. Daha fazla bilgi help execiçin terminalinizde çalıştırın .

    Bu özel durumda, akış 3 akış 1'e (STDOUT) yönlendirilir, yani akış 3'e daha sonra göndereceğimiz her şey terminalimizde normalde STDOUT'a yazdırılmış gibi görünür.

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

    Bu çizgi birçok bölümden ve sözdizimsel yapılardan oluşur:

    • result=$(...)
      Bu yapı, parantez içindeki komutu yürütür ve çıktıyı (STDOUT) bash değişkenine atar result. Üzerinden okunabilir $result. Bütün bunlar bir şekilde veeeery tuvalette açıklanmaktadır man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Bu komut, verilen METİN ile bir TUI kutusu, bir metin giriş alanı ve iki düğme OK ve CANCEL gösterir. Tamam seçilirse, komut 0 durumuyla çıkar ve girilen metni STDERR'a yazdırır, İPTAL seçilirse, kod 1 ile çıkar ve hiçbir şey yazdırmaz. Daha fazla bilgi için okuyun man dialog.

    • 2>&1 1>&3
      Bunlar iki yönlendirme komutudur. Bunlar sağdan sola yorumlanacaktır:

      1>&3 komutun akışı 1'i (STDOUT) özel akış 3'e yönlendirir.

      2>&1 daha sonra komutun akış 2'sini (STDERR) akış 1'e (STDOUT) yönlendirir.

      Bu, komutun STDOUT'a yazdırdığı her şeyin artık akış 3'te göründüğü anlamına gelirken, STDERR'da görünmesi amaçlanan her şey şimdi STDOUT'a yönlendiriliyor.

    Böylece tüm satır bir metin istemi görüntüler (STDOUT'ta akış 3'e yönlendirilir, kabuk tekrar STDOUT'a yeniden yönlendirir - exec 3>&1komutu görür ) ve girilen verileri (STDERR üzerinden döndürülür, sonra STDOUT'a yönlendirilir) Bash değişkenine result.

  • exitcode=$?

    Bu kod, önceden çalıştırılan komutun çıkış kodunu (buradan dialog) ayrılmış Bash değişkeni aracılığıyla alır $?(her zaman son çıkış kodunu tutar) ve basitçe kendi Bash değişkenimizde saklar exitcode. $exitcodeTekrar okunabilir . Bu konuda daha fazla bilgi arayabilirsiniz man bash, ancak bu biraz zaman alabilir ...

  • exec 3>&-

    Dahili Bash execkabuğa bir akış yeniden yönlendirmesi uygulamak için kullanılabilir, yani aşağıdaki tüm komutları etkiler. Daha fazla bilgi help execiçin terminalinizde çalıştırın .

    Bu özel durumda, akış 3 "akış -" öğesine yönlendirilir, bu sadece kapatılması gerektiği anlamına gelir. Akış 3'e gönderilen veriler artık hiçbir yere yönlendirilmeyecek.

  • echo $result $exitcode

    Bu basit echokomut (daha fazla bilgi hakkında man echo) sadece iki Bash değişkeninin içeriğini resultve exitcodeSTDOUT'a yazdırır . Burada artık açık veya örtülü akış yönlendirmeleri olmadığından, bunlar gerçekten STDOUT'ta görünecek ve bu nedenle terminalde görüntülenecektir. Ne mucize! ;-)

Özet:

İlk olarak, kabuğu özel terminal 3'e gönderdiğimiz her şeyi STDOUT'a yönlendirmek için ayarladık, böylece terminalimizde görünecek.

Sonra dialogkomutu çalıştırırız, orijinal STDOUT'u özel akışımız 3'e yönlendiririz, çünkü sonunda görüntülenmesi gerekir, ancak STDOUT akışını başka bir şey için geçici olarak kullanmamız gerekir.
Diyalog penceresinin kullanıcı girişinin döndürüldüğü komutun orijinal STDERR'unu daha sonra STDOUT'a yönlendiriyoruz.
Şimdi STDOUT'u (STDERR'den yeniden yönlendirilen verileri tutan) yakalayabilir ve değişkenimizde saklayabiliriz $result. Şimdi istenen kullanıcı girdisini içerir!

Ayrıca dialogkomutun çıkış kodunu da istiyoruz , bu da bize Tamam mı yoksa İPTAL mi tıklandığını gösterir. Bu değer ayrılmış Bash değişkeninde sunulur $?ve sadece kendi değişkenimize kopyalarız $exitcode.

Bundan sonra, daha fazla yeniden yönlendirmeyi durdurmak için artık ihtiyaç duymadığımız için akış 3'ü tekrar kapatıyoruz.

Son olarak, normalde her iki değişkenin içeriğini ( $resultdiyalog penceresinin kullanıcı girişi) ve $exitcode(OK için 0, CANCEL için 1) terminale çıkarırız.


Bence kullanmak execgereksiz derecede karmaşık. Neden sadece çıkışını biz --stdoutseçmiyoruz dialogveya yönlendirmiyoruz 2>&1 >/dev/tty?
jarno

Lütfen cevabımı gör .
jarno

3
Mükemmel cevap! Bununla birlikte, yanlış olan bir notunuz olduğuna inanıyorum - "Bunlar sağdan sola yorumlanacak" diyorsunuz, ancak bunun doğru olmadığına inanıyorum. Gnu.org/software/bash/manual/html_node/Redirections.html kılavuzundan , yeniden yönlendirmelerin karşılaşıldıklarında (yani soldan sağa) gerçekleştiğini gösterir
ralfthewise

14

İletişim kutusunun kendi araçlarını kullanma: --output-fd flag

İletişim için man sayfasını okursanız --output-fd, varsayılan olarak STDERR'a gitmek yerine çıktının nereye gittiğini (STDOUT 1, STDERR 2) açıkça ayarlamanıza izin veren bir seçenek vardır .

Aşağıdaki örnekte, dialogkomutun MYVAR'a kaydetmeme izin veren dosya tanımlayıcı 1'e gitmesi gerektiğini açıkça belirten örnek komutu çalıştırdığımı görebilirsiniz .

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

resim açıklamasını buraya girin

Adlandırılmış yöneltmeleri kullanma

Çok fazla gizli potansiyele sahip alternatif yaklaşım, adlandırılmış boru olarak bilinen bir şey kullanmaktır .

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

resim açıklamasını buraya girin

User.dz'nin alternatif yaklaşımla verdiği cevaba daha ayrıntılı bir bakış

User.dz ve ByteCommander'ın bu konudaki açıklaması , hem iyi bir çözüm hem de ne yaptığına genel bir bakış sağlar. Ancak, daha derin bir analizin neden işe yaradığını açıklamak için faydalı olabileceğine inanıyorum .

Her şeyden önce, iki şeyi anlamak önemlidir: çözmeye çalıştığımız sorun nedir ve uğraştığımız kabuk mekanizmalarının altında yatan çalışmalar nelerdir. Görev, bir komutun çıkışını komut değiştirme yoluyla yakalamaktır. Herkesin bildiği basit bir bakış altında, komut ikameleri stdoutbir komutu yakalar ve başka bir şey tarafından yeniden kullanılmasına izin verir. Bu durumda, result=$(...)parça, belirtilen komutun çıktısını ...adlı bir değişkene kaydetmelidir result.

Kaputun altında, komut ikamesi aslında bir alt işlem (çalışan gerçek komut) ve okuma işlemi (çıktıyı değişkene kaydeder) olan boru olarak uygulanır. Bu, sistem çağrılarının basit bir izlemesiyle belirgindir. Dosya tanımlayıcı 3'ün borunun okuma ucu, 4'ün ise yazma sonu olduğuna dikkat edin. Dosya tanımlayıcısı 1'e echoyazılan alt işlemi için stdoutbu dosya tanımlayıcısı, aslında borunun yazma sonu olan dosya tanımlayıcı 4'ün kopyasıdır. stderrBurada rol oynamadığına dikkat edin , çünkü sadece bir boru bağlantısı 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 +++

Bir saniye orijinal cevaba geri dönelim. Artık dialogTUI kutusunu yazar stdout, yanıtlar stderrve komut ikamesi içinde stdoutbaşka bir yere pipetlendiğini bildiğimizden beri, çözümün bir parçası zaten var - dosya tanımlayıcılarını stderrokuyucu sürecine bağlanacak şekilde yeniden sarmamız gerekiyor. Bu 2>&1cevabın bir parçası. Ancak TUI kutusu ile ne yapıyoruz?

Burada dosya tanımlayıcı 3 dup2()devreye girer. Syscall, dosya tanımlayıcıları çoğaltmamıza izin verir, bu da onları etkili bir şekilde aynı yere yönlendirir, ancak bunları ayrı olarak manipüle edebiliriz. Kontrol terminali bağlı işlemlerin dosya tanımlayıcıları aslında belirli terminal cihazını gösterir. Bunu yaparsanız bu açıktır

$ 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şu anki sahte terminal cihazım nerede . Böylece, bir şekilde bu hedefi kaydedebilirsek, yine de TUI kutusunu terminal ekranına yazabiliriz. İşte exec 3>&1böyle. command > /dev/nullÖrneğin, yeniden yönlendirme ile bir komut çağırdığınızda , kabuk stdout dosya tanımlayıcısını geçirir ve daha sonra dup2()bu dosya tanımlayıcısını yazmak için kullanır /dev/null. execKomut gerçekleştirdiği benzer bir şeydup2() bütün kabuk oturumu için dosya tanıtıcı, böylece herhangi bir komut devralır zaten yönlendirildi dosya tanımlayıcısı yapma. İle aynı exec 3>&1. Dosya tanımlayıcı 3şimdi kontrol eden terminale başvuracak / işaret edecek ve bu kabuk oturumunda çalışan herhangi bir komut onu tanıyacaktır.

Bu result=$(dialog --inputbox test 0 0 2>&1 1>&3);durumda, kabuk iletişim kutusunun yazılması için bir boru oluşturur, ancak 2>&1önce komutun dosya tanımlayıcısını 2 o borunun yazma dosyası tanımlayıcısına çoğaltır (böylece çıktının borunun sonuna ve değişkene gitmesini sağlar) , dosya tanımlayıcı 1 3 üzerine çoğaltılır. Bu, dosya tanımlayıcı 1'in hala kontrol terminaline başvurmasını sağlar ve TUI iletişim kutusu ekranda görünecektir.

Şimdi, aslında sürecin akım kontrol terminali için bir kısa el var /dev/tty. Böylece, çözüm, dosya tanımlayıcıları kullanılmadan basitçe basitleştirilebilir:

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

Hatırlanması gereken önemli noktalar:

  • dosya tanımlayıcıları her komut tarafından kabuktan devralınır
  • komut ikamesi boru olarak uygulanır
  • çoğaltılmış dosya tanımlayıcıları orijinaliyle aynı yere atıfta bulunacaktır, ancak her dosya tanımlayıcısını ayrı olarak değiştirebiliriz

Ayrıca bakınız


Manpage ayrıca, --stdoutseçeneğin tehlikeli olabileceğini ve bazı sistemlerde kolayca başarısız olabileceğini söylüyor --output-fd 1ve aynı şeyi yaptığını düşünüyorum : --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.- Ancak, adlandırılan boru fikri harika!
Bayt Komutanı

@ByteCommander "Başarısız olabilir" çok ikna edici değildir, çünkü bu örnek vermez. Ayrıca, hiçbir şeyden bahsetmiyorlar --output-fd, burada kullandığım seçenek değil --stdout. İkincisi, iletişim kutusu önce stdout'a çizilir, döndürülen çıktı ikinci olur. Bu iki şeyi aynı anda yapmıyoruz. Ancak, --output-fd özellikle fd 1 (STDOUT) kullanılmasını gerektirmez. Kolayca başka bir dosya tanımlayıcısına yönlendirilebilir
Sergiy Kolodyazhnyy

Emin değilim, belki her yerde çalışır, belki sadece çoğu sistemde çalışır. Benimki üzerinde çalışıyor ve manpage, emin olduğum tek şey dikkatle benzer bir seçenek kullandığını söylüyor. Ama daha önce de söylediğim gibi, +1 zaten adlandırılmış borular için hak ediyor.
Bayt Komutanı

Dengeyi korumak için burada yorum yapmalıyım. Bana göre, bu tek doğrudan kanonik cevap olabilir (1) sadece aynı aracı kullanır ve herhangi bir harici araç olmadan seçenekleri uygular (2) Ubuntu'da çalışır ve AU'nun ne olduğu hakkında. : / ne yazık ki OP bu soruyu terk ediyor gibi görünüyor.
user.dz

Burada normal dosya yerine adlandırılmış yöneltme kullanmanın avantajı nedir? Kullandıktan sonra boruyu silmek istemiyor musunuz?
jarno

7

: DI açıklayamıyor !!! Referansta ne söylediklerini anlayabiliyorsanız: Gelişmiş Bash-Scripting Kılavuzu: Bölüm 20. G / Ç Yönlendirme , yeni bir cevap yazın ve size 50rep vereceğim

Ödül verildi, açıklama için bkz. ByteCommander cevabı . :) Bu tarihin bir parçası.

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

Kaynak: bash iletişim kutusu değişkenleri düzgün bir şekilde yakalamıyor
Başvuru: Gelişmiş Bash-Scripting Kılavuzu: Bölüm 20. G / Ç Yeniden Yönlendirme


bu teklif hala geçerli mi? Sanırım orada bir buçuk yıl önce ne bulduğunu açıklayabilirdim ... :-)
Byte Komutanı

@ByteCommander, ancak bunu sağlayabilirseniz, size vereceğim, sözlerimde olacağım: D.
user.dz

@ByteCommander, lütfen, gönderdikten sonra bana ping atın.
user.dz

1
Tamamlandı! askubuntu.com/a/704616/367990 Umarım her şeyi anlarsınız ve "Eureka!" an. :-D Bir şey net bırakılmadıysa bir yorum bırakın.
Bayt Komutanı

4

Bu benim için çalışıyor:

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

Manuel sayfa dialog--stdout hakkında bilgi verir:

Standart çıkışa doğrudan çıkış. Bu seçenek Xdialog ile uyumluluk için sağlanmıştır, ancak lanetler normalde ekran güncellemelerini standart çıktıya yazdığından, taşınabilir komut dosyalarında kullanılması önerilmez. Bu seçeneği kullanırsanız, iletişim kutusu ekranı yazabilmesi için terminali yeniden açmayı dener. Platforma ve ortamınıza bağlı olarak, bu başarısız olabilir.

Herkes hangi platformda veya ortamda çalışmadığını söyleyebilir mi? Bunun yerine dialogçıktıyı yeniden yönlendirmek 2>&1 >/dev/ttydaha iyi çalışıyor mu?


4

Başka birinin de Google'dan buraya gelmesi ve bu soru özellikle bash istemesine rağmen, başka bir alternatif daha var:

Zenity kullanabilirsiniz . Zenity a, grafik programı olabilir Bash komut dosyaları içinde kullanılır. Ancak, user877329'un haklı olarak işaret ettiği gibi, bu bir X sunucusu gerektirecektir.

sudo apt-get install zenity

Sonra senaryonuzda:

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

Yararlı bağlantı .


3
X sunucusu yoksa
user877329

1
OP bilmek istiyor dialog. Gelip "? Nasıl Python bunu yazmak ve bu do" sormak gibi olacak, ama beni vurmayı vermek - ben bu farklı bir şekilde yapılabilir çok mutluyum, ama bu Soruyorum bu değil
Sergiy Kolodyazhnyy

@ Yorumunuzu geçersiz yapın, cevabım değil: Yardımcı program OP tarafından istenen çözüme mükemmel bir şekilde geçerli ve basit bir alternatif sunuyor.
Wtower

3

Sneetsher tarafından verilen cevap biraz daha zarif, ama neyin yanlış olduğunu açıklayabilirim: değeri geri çukurların $$içinde farklıdır (çünkü yeni bir kabuk başlatır $$ve mevcut kabuğun PID'sidir). Dosya adını bir değişkene koymak ve bunun yerine bu değişkene başvurmak isteyeceksiniz.

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

Bu durumda, geçici dosyadan kaçınmak daha iyi bir çözüm olacaktır, ancak geçici bir dosyadan kaçınamayacağınız birçok durum olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.