Jak czytać dane wprowadzane przez użytkownika podczas korzystania ze skryptu w potoku


10

Ogólny problem

Chcę napisać skrypt, który wchodzi w interakcję z użytkownikiem, nawet jeśli jest on w środku łańcucha potoków.

Konkretny przykład

Konkretnie, zajmuje to filelub stdinwyświetla linie (z numerami linii), prosi użytkownika o wprowadzenie wyboru lub numerów linii, a następnie drukuje odpowiednie linie do stdout. Nazwijmy ten skrypt selector. Więc w zasadzie chcę być w stanie to zrobić

grep abc foo | selector > myfile.tmp

Jeśli foozawiera

blabcbla
foo abc bar
quux
xyzzy abc

następnie selectorprzedstawia mi (na terminalu, nie w myfile.tmp!) opcje

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

po czym piszę

2-3

i skończyć z

foo abc bar
xyzzy abc

jako zawartość myfile.tmp.

Mam gotowy skrypt selektora i zasadniczo działa idealnie, jeśli nie przekierowuję danych wejściowych i wyjściowych. Więc

selector foo

zachowuje się tak, jak chcę. Jednak podczas przesyłania strumieniowego razem, jak w powyższym przykładzie, selectordrukuje przedstawione opcje myfile.tmpi próbuje odczytać zaznaczenie z grepped input.

Moje podejście

Próbowałem użyć -uflagi read, jak w

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

ale to nie robi tego, na co liczyłem.

P: Jak uzyskać faktyczną interakcję użytkownika?


zrób skrypt i zapisz dane wyjściowe w zmiennej, a następnie obecny użytkownik chcesz?
Hackaholic

@Hackaholic - Nie jestem pewien, co masz na myśli. Chcę skryptu, który może być umieszczony w dowolnej sekwencji potoku (tj. Sposób uniksowy). Podałem dokładny przykład powyżej, ale z pewnością nie jest to jedyny przypadek użycia, jaki mam na myśli.
jmc

1
Użyjcmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv - Ciekawe! Mam teraz, 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; }'który działa całkiem dobrze. Jednak grep b foo | selector | wc -ltutaj się psuje. Wszelkie pomysły, jak to naprawić? Nawiasem mówiąc, to rangeselect, którego użyłem, można znaleźć na pastebin.com/VAxTSSHs . Jest to prosty skrypt AWK, który drukuje wiersze pliku odpowiadające danemu zakresowi numerów bielizny. (Zakresy mogą być takie jak „3-10, 12,14,16-20”.)
jmc

1
Nie rób aliastego raczej selector() { all of that stuff...; }w funkcji. aliases zmienia nazwy prostych poleceń, podczas gdy funkcje pakują złożone polecenie w jedno proste polecenie .
mikeserv

Odpowiedzi:


8

Używanie /proc/$PPID/fd/0jest zawodne: rodzic selectorprocesu może nie mieć terminala jako danych wejściowych.

Jest to standardowy ścieżka że zawsze odnosi się do terminala bieżącego procesu: /dev/tty.

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

lub

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

1
Dzięki, to rozwiązuje mój problem. Odpowiedź jest jednak minimalistyczna. Wydaje mi się, że skorzystanie z uwzględnienia niektórych rad Mikeserv w komentarzach do pytania.
jmc

2

Napisałem małą funkcję: nie odpowie na to, o co prosiłeś o łączenie rur, ale rozwiąże twój problem.

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)"
)

Funkcja odwraca wszystkie argumenty, którym ją natychmiast przekazujesz grep. Jeśli użyjesz globu powłoki, aby określić pliki, które powinien z niego odczytać, zwróci wszystkie dopasowania we wszystkich plikach, zaczynając od pierwszego w kolejności globu, a kończąc na ostatnim dopasowaniu.

grepprzekazuje dane wyjściowe, do nlktórych numeruje każdą linię i które przekazuje dane wyjściowe, do teektórych duplikuje dane wyjściowe zarówno do, jak stdouti do /dev/tty. Oznacza to, że dane wyjściowe z potoku są jednocześnie drukowane zarówno w tablicy argumentów funkcji, gdzie są dzielone na \newline, jak i na terminalu, gdy działa.

Następnie _in()funkcja próbuje dokonać readselekcji, jeśli jest co najmniej 1 wynik z poprzedniej akcji maksymalnie pięć razy. Wybór może składać się tylko z liczb oddzielonych spacjami lub zakresów liczb oddzielonych -. Jeśli cokolwiek innego jest read (w tym pusta linia) , spróbuje ponownie - ale tylko, jak poprzednio, maksymalnie pięć razy.

Na koniec _out()funkcja analizuje wybór użytkownika i rozszerza zawarte w nim zakresy. "${[num]}"Dla każdego wypisuje swoje wyniki w formie - dopasowując w ten sposób wartość linii przechowywanych w inf()tablicy arg. Dane wyjściowe są evaledytowane jako argumenty, w printfzwiązku z czym wypisuje tylko linie wybrane przez użytkownika.

Wyraźnie readpochodzi z terminala i drukuje tylko Select:menu, stderrdzięki czemu jest przyjazny dla potoków. Na przykład następujące prace:

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

Możesz jednak użyć dowolnych opcji grepi dowolnej liczby nazw plików, które możesz mu podać . Oznacza to, że możesz użyć dowolnego innego niż jeden - ponieważ efekt uboczny jego analizy składniowej $IFSnie będzie działał, jeśli szukasz pustych linii. Ale kto chciałby wybrać z ponumerowanej listy pustych linii?

Ostatnia uwaga, ponieważ ponieważ działa to poprzez bezpośrednie przetłumaczenie numerycznych danych wejściowych użytkownika na numeryczne parametry pozycyjne zapisane w tablicy argumentów funkcji, wynik będzie taki, jak wybierze użytkownik, tyle razy, ile wybierze użytkownik, i w dowolnej kolejności, którą wybierze użytkownik to.

Na przykład:

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 to był tylko pomysł, a nie cały skrypt, i jedno, mówisz o teście, oryginalny plik jest tylko na dysku, więc bierzesz go. więc myślę, że nie jest to żaden problem ani dodatkowy wysiłek, aby to przetestować
Hackaholic

@ Mikeserv Tak, masz rację, nie sprawdziłem wszystkich rzeczy, takich jak nieprawidłowe dane wejściowe i tak dalej. dzięki za uwagę
Hackaholic

@ Mikeserv Znam wszystkie podstawowe programy programowania powłoki, czy możesz poprowadzić mnie jak przejść do zaawansowanych
Hackaholic

tak, na pewno będę zadowolony, edytuj
Hackaholic
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.