Як читати введення користувача при використанні сценарію в трубі


10

Загальна проблема

Я хочу написати сценарій, який взаємодіє з користувачем, хоча він знаходиться в середині ланцюга труб.

Конкретний приклад

Конкретніше, він займає fileабо stdin, відображає рядки (з номерами рядків), просить користувача ввести вибір або номери рядків, а потім друкує відповідні рядки stdout. Давайте назвемо цей сценарій selector. Тоді в основному я хочу вміти це робити

grep abc foo | selector > myfile.tmp

Якщо fooмістить

blabcbla
foo abc bar
quux
xyzzy abc

потім selectorподає мені (на терміналі, не в myfile.tmp!) з опціями

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

після чого я набираю

2-3

і в кінцевому підсумку

foo abc bar
xyzzy abc

як зміст myfile.tmp.

У мене запущений і запущений селекторний сценарій, і в основному він працює відмінно, якщо я не переспрямовую введення та виведення. Тому

selector foo

поводиться так, як я хочу. Однак, з'єднуючи речі разом, як у наведеному вище прикладі, selectorдрукується представлені параметри myfile.tmpта намагається прочитати виділення із введеного даних.

Мій підхід

Я намагався використовувати -uпрапор read, як у

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

але це не робить те, що я сподівався.

З: Як отримати фактичну взаємодію з користувачем?


зробіть сценарій і збережіть результат у змінній, а потім теперішній користувач хочете, що хочете ??
Хакаголік

@Hackaholic - я не впевнений, що ти маєш на увазі. Я хочу, щоб сценарій міг бути розміщений у будь-якій послідовності конвеєра (тобто Unix). Я наводив детальний приклад вище, але це, безумовно, не єдиний випадок використання, який я маю на увазі.
jmc

1
Використанняcmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv - Цікаво! У мене зараз 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; }'це працює досить непогано. Однак grep b foo | selector | wc -lтут перерви. Будь-які ідеї, як це виправити? До речі, те, rangeselectщо я використав, можна знайти на pastebin.com/VAxTSSHs . Це простий сценарій AWK, який друкує рядки файлу, що відповідають заданому діапазону рядків. (Діапазон може бути таким, як "3-10, 12,14,16-20".)
jmc

1
Не роби aliasце, скоріше selector() { all of that stuff...; }у функцію. aliases перейменувати прості команди, тоді як функції пакують складні команди в одну просту команду .
mikeserv

Відповіді:


8

Використання /proc/$PPID/fd/0недостовірне: батьківський selectorпроцес може не мати термінал як свій вхід.

Існує стандартний шлях , який завжди ставиться до терміналу з поточною діяльністю в: /dev/tty.

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

або

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

1
Дякую, це вирішує мою проблему. Однак відповідь трохи мінімалістичний. Я думаю, що це може виграти від включення в коментарі до питання деяких порад mikeserv.
jmc

2

Я написав невелику функцію: вона не відповість на те, що ви попросили ланцюг труби, але вирішить вашу проблему.

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

Функція перевертає всі аргументи, яким ви її негайно даєте grep. Якщо ви використовуєте глобул оболонки для вказівки файлів, які слід прочитати з нього, він поверне всі збіги у всіх файлах, починаючи з першого в глобальному порядку і закінчуючи останнім збігом.

grepпередає свій вихід, на nlякий номери кожного рядка, і який передає свій висновок, на teeякий дублює його вихід і на, stdoutі на /dev/tty. Це означає, що вихід з конвеєра одночасно друкується як до масиву аргументів функції, де він розділений на \newlines, так і на термінал, коли він працює.

Далі _in()функція намагається readвибрати, якщо є щонайменше 1 результат від попередньої дії максимум п'ять разів. Вибір може складатися з лише чисел, розділених пробілами, або діапазонів чисел, розділених на -. Якщо що-небудь ще є read (включаючи порожній рядок), він спробує ще раз - але, як і раніше, максимум п’ять разів.

Останнє _out()функція аналізує вибір користувача та розширює будь-які діапазони в ньому. Він друкує свої результати у формі "${[num]}"для кожного - тим самим відповідає значенню рядків, що зберігаються в inf()масиві arg ar. Цей вихід видається у evalвигляді аргументів, до printfяких друкується лише рядки, які користувач обрав.

Це явно readз терміналу і друкує лише Select:меню, stderrі це дуже зручно для конвеєра. Наприклад, такі роботи:

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

Але ви можете використовувати будь-які варіанти, які ви б дали, grepі будь-яку кількість імен файлів, які ви також можете передавати. Тобто, ви можете використовувати будь-який, крім одного виду - оскільки побічний ефект його входу з розбору з $IFSним не буде працювати, якщо ви шукаєте порожні рядки. Але хто хотів би вибрати з нумерованого списку порожніх рядків?

Останнє зауважте, що оскільки це працює шляхом прямого перекладу числового вводу користувача в числові позиційні параметри, що зберігаються в масиві аргументів функції, то вихід буде будь-яким, що вибере користувач стільки разів, скільки вибере його, і в будь-якому порядку, який вибере користувач. це.

Наприклад:

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 це була лише ідея, а не весь сценарій, і одне, ви говорите про тест, оригінальний файл є лише на диску, тому ви берете з них. тому я думаю, що це не проблема чи додаткові зусилля для перевірки цього
Hackaholic

@mikeserv Так, ви праві, я не підтвердив все, як неправильне введення даних і все. дякую за вашу думку
Hackaholic

@mikeserv Я знаю всі основні програми програмування оболонок, чи можна мені керувати, як бути в
курсі

так, звичайно, я буду радий редагувати це
Hackaholic
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.