Boru içinde komut dosyası kullanırken kullanıcı girdisi nasıl okunur


10

Genel problem

Bir boru zincirinin ortasında olmasına rağmen kullanıcıyla etkileşime giren bir komut dosyası yazmak istiyorum.

Somut örnek

Somut olarak, bir fileveya alır stdin, satırları görüntüler (satır numaraları ile), kullanıcıdan bir seçim veya satır numaraları girmesini ister ve ardından karşılık gelen satırları yazdırır stdout. Bu betik diyelim selector. Sonra temel olarak, yapabilmek istiyorum

grep abc foo | selector > myfile.tmp

Eğer fooiçeriyor

blabcbla
foo abc bar
quux
xyzzy abc

sonra selectorbana seçeneklerle (terminalde değil myfile.tmp!) sunar

1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:

bundan sonra yazıyorum

2-3

ve sonunda

foo abc bar
xyzzy abc

içeriği olarak myfile.tmp.

Bir seçici komut dosyası var ve çalışıyor ve giriş ve çıkış yönlendirmezseniz temelde mükemmel çalışıyor. Yani

selector foo

istediğim gibi davranıyor. Bununla birlikte, yukarıdaki örnekte olduğu gibi bir şeyleri birbirine bağlarken selector, sunulan seçenekleri yazdırılan myfile.tmpgirdiye yazdırır ve seçili öğeden bir seçim okumaya çalışır.

Benim yaklaşımım

Ben kullanmayı denedim -ubayrağını readolduğu gibi,

exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "

ama bu umduğum şeyi yapmaz.

S: Gerçek kullanıcı etkileşimini nasıl alabilirim?


bir komut dosyası yapmak ve çıktıyı değişkene kaydetmek ve sonra mevcut kullanıcı istediğiniz istiyorum ??
Hackaholic

@Hackaholic - Ne demek istediğinden emin değilim. Her türlü boru hattı dizisi (yani Unix yolu) yerleştirilebilir bir komut dosyası istiyorum. Yukarıda ayrıntılı bir örnek verdim, ancak kesinlikle aklımdaki tek kullanım durumu bu değil.
jmc

1
Kullanımcmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv - İlginç! Şimdi alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'hangisi oldukça iyi çalışıyor. Ancak grep b foo | selector | wc -lburadan kopar. Bunu nasıl düzeltebileceğine dair fikrin var mı? Bu arada, rangeselectkullandığım pastebin.com/VAxTSSHs adresinde bulunabilir . Belirli bir dizi çamaşır grubuna karşılık gelen bir dosyanın satırlarını yazdıran basit bir AWK betiğidir. (Aralık, "3-10, 12,14,16-20" gibi şeyler olabilir.)
jmc

1
Yapma aliasziyade o, selector() { all of that stuff...; }bir fonksiyonun içine. aliases basit komutları yeniden adlandırırken , işlevler bir bileşik komutu tek bir basit komut halinde toplar .
mikeserv

Yanıtlar:


8

Kullanmak /proc/$PPID/fd/0güvenilmez: selectorişlemin üst öğesi giriş olarak terminale sahip olmayabilir.

Bir yoktur standart yol her zaman geçerli sürecin terminale ifade ettiğini: /dev/tty.

nl "$INPUT" >/dev/tty
read -p"Select options: " </dev/tty

veya

exec </dev/tty >/dev/tty
nl "$INPUT"
read -p"Select options: "

1
Teşekkürler, bu benim sorunumu çözdü. Cevap biraz minimalist. Sanırım mikeserv'in tavsiyelerinden bazılarını soruya yapılan yorumlara dahil etmekten fayda sağlayabilir.
jmc

2

Küçük bir işlev yazdım: boru zincirlemesini sorduğunuz şeylere cevap vermez, ancak sorununuzu çözer.

inf() ( [ -n "$ZSH_VERSION" ] && emulate sh
        unset n i c; set -f; tab='      ' IFS='
';      _in()   until [ "$((i+=1))" -gt 5 ] && exit 1
                printf '\nSelect: '
                read -r c && [ -n "${c##*[!- 0-9]*}" ]
                do echo "Invalid selection."
                done
        _out()  for n do i=; [ "$n" = . ]  &&
                printf '"${%d#*$tab}" ' $c ||
                until c="${c#*.} ${i:=${n%%-*}}"
                [ "$((i+=1))" -gt "${n#*-}" ]
                do :; done; done
set -- $(grep "$@"|nl -w1 -s "$tab"|tee /dev/tty)
i=$((($#<1)*5)); _in </dev/tty >/dev/tty
eval "printf '%s\n' $(c=$c\ . IFS=\ ;_out $c)"
)

İşlev, hemen verdiğiniz tüm bağımsız değişkenleri ters çevirir grep. Bir kabuk glob kullanırsanız, okumanız gereken dosyaları belirtmek için, glob sırasındaki ilkden başlayıp son eşleşmeyle biten tüm dosyalardaki tüm eşleşmeleri döndürür.

grepçıktısı geçen nlher satırı olan ve çıktısı geçtiği numaraları teeher iki çıktısı örtüşmesinde stdoutve /dev/tty. Bu, boru hattından çıktının eşzamanlı olarak hem işlevin argüman dizisine \newlines üzerinde bölündüğü hem de terminale çalışırken yazdırıldığı anlamına gelir .

Daha sonra , bir önceki eylemden en az 1 kez en fazla beş sonuç alındığında _in()işlev readbir seçim yapmaya çalışır . Seçim yalnızca boşluklarla ayrılmış sayılardan veya başkalarıyla ayrılmış sayı aralıklarından oluşabilir -. Başka bir şey varsa read (boş bir çizgi dahil) tekrar deneyecek - ancak daha önce olduğu gibi en fazla beş kez.

Son olarak _out()işlev kullanıcının seçimini ayrıştırır ve içindeki tüm aralıkları genişletir. Sonuçlarını "${[num]}"her biri için formda yazdırır, böylece inf()'arg' dizisinde saklanan satırların değerleriyle eşleşir. Bu çıktı bağımsız değişkenler olarak evaldüzenlenir ve printfbu nedenle yalnızca kullanıcının seçtiği satırları yazdırır.

Açıkça readterminalden ve sadece Select:menü yazdırır stderrve bu yüzden bol boru hattı dostu. Örneğin, aşağıdakiler çalışır:

seq 100 |inf 3|grep 8
1       3
2       13
3       23
4       30
5       31
6       32
7       33
8       34
9       35
10      36
11      37
12      38
13      39
14      43
15      53
16      63
17      73
18      83
19      93

Select: 6 9 12-18
38
83

Ancak vereceğiniz seçenekleri grepve istediğiniz sayıda dosya adını da kullanabilirsiniz. Yani, bir tür dışında herhangi bir tür kullanabilirsiniz - $IFSboş satırları arıyorsanız , ayrıştırma girişinin yan etkisi olarak çalışmaz. Ancak, numaralı boş satır listesinden kim seçim yapmak ister?

Bu, sayısal kullanıcı girişini doğrudan işlevin bağımsız değişken dizisinde depolanan sayısal konum parametrelerine çevirerek çalıştığı için, çıktı, kullanıcının seçtiği kadar ve kullanıcının seçtiği sırada kullanıcının seçtiği her şey olacaktır. o.

Örneğin:

seq 1000 | inf 00\$

1       100
2       200
3       300
4       400
5       500
6       600
7       700
8       800
9       900
10      1000

Select: 4-8 1 1 3-6
400
500
600
700
800
100
100
300
400
500
600

@mikeserv sadece bir fikirdi, tüm senaryo değil ve bir şey, testten bahsediyorsunuz, orijinal dosya sadece diskte, bu yüzden onlardan alıyorsunuz. bu yüzden bunu test etmek için bir sorun veya ekstra çaba olmadığını düşünüyorum
Hackaholic

@mikeserv evet haklısın, yanlış girdi ve her şey gibi her şeyi doğrulamamıştım.
Puanınız

@mikeserv kabuk programlama tüm temel biliyorum, u nasıl ileri gitmek için bana rehberlik edebilir
Hackaholic

evet emin o memnun olacak düzenleme
Hackaholic 23
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.