Problema geral
Quero escrever um script que interaja com o usuário, mesmo que esteja no meio de uma cadeia de tubos.
Exemplo concreto
Concretamente, ele pega a file
ou stdin
exibe linhas (com números de linha), solicita ao usuário que insira uma seleção ou números de linha e imprime as linhas correspondentes em stdout
. Vamos chamar esse script selector
. Então, basicamente, eu quero poder fazer
grep abc foo | selector > myfile.tmp
Se foo
contém
blabcbla
foo abc bar
quux
xyzzy abc
então selector
me apresenta (no terminal, não no myfile.tmp
!) opções
1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:
após o que eu digito
2-3
e acabar com
foo abc bar
xyzzy abc
como conteúdo de myfile.tmp
.
Eu tenho um script seletor instalado e funcionando, e basicamente funciona perfeitamente se eu não redirecionar a entrada e a saída. assim
selector foo
se comporta como eu quero. No entanto, ao canalizar as coisas juntas, como no exemplo acima, selector
imprime as opções apresentadas myfile.tmp
e tenta ler uma seleção da entrada grepped.
Minha abordagem
Eu tentei usar a -u
bandeira de read
, como em
exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "
mas isso não faz o que eu esperava.
P: Como obtenho uma interação real do usuário?
cmd | { some processing; read var </dev/tty; } | cmd
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; }'
que funciona muito bem. No entantogrep b foo | selector | wc -l
quebra aqui. Alguma idéia de como consertar isso? A propósito, orangeselect
que eu usei pode ser encontrado em pastebin.com/VAxTSSHs . É um script AWK simples que imprime as linhas de um arquivo correspondente a um determinado intervalo de números de roupa. (Os intervalos podem ser coisas como "3-10, 12,14,16-20".)alias
isso, sim,selector() { all of that stuff...; }
em uma função.alias
renomeia comandos simples, enquanto as funções agrupam um comando composto em um único comando simples .Respostas:
O uso
/proc/$PPID/fd/0
não é confiável: o pai doselector
processo pode não ter o terminal como entrada.Há um caminho padrão que sempre se refere ao terminal do processo atual:
/dev/tty
.ou
fonte
Eu escrevi uma pequena função: ela não responde sobre o que você pediu para encadear tubos, mas resolve o seu problema.
A função vira todos os argumentos que você fornece imediatamente
grep
. Se você usar um shell glob para especificar os arquivos que ele deve ler, retornará todas as correspondências em todos os arquivos, começando com o primeiro na ordem glob e terminando com a última correspondência.grep
passa sua saída paranl
quais números cada linha e que passa sua saída para atee
qual duplica sua saída parastdout
e para/dev/tty
. Isso significa que a saída do pipeline é impressa simultaneamente na matriz de argumentos da função, onde é dividida nas\n
linhas ew e no terminal, conforme funciona.Em seguida, a
_in()
função tentaread
em uma seleção se houver pelo menos 1 resultado da ação anterior no máximo cinco vezes. A seleção pode consistir em apenas números separados por espaços, ou então intervalos de números separados por-
. Se houver mais alguma coisaread
(incluindo uma linha em branco), ela tentará novamente - mas apenas, como antes, no máximo cinco vezes.Por último, a
_out()
função analisa a seleção do usuário e expande os intervalos nela. Ele imprime seus resultados no formulário"${[num]}"
para cada um - correspondendo, assim, ao valor das linhas armazenadas nainf()
matriz arg. Essa saída éeval
editada como args, para aprintf
qual, portanto, imprime apenas as linhas que o usuário selecionou.Ele é explicitamente
read
do terminal e apenas imprime oSelect:
menu para,stderr
portanto, é bastante amigável ao pipeline. Por exemplo, o seguinte funciona:Mas você pode usar todas as opções que você daria
grep
e qualquer número de nomes de arquivos que também possa ser entregue. Ou seja, você pode usar qualquer tipo, exceto um - porque o efeito colateral de sua entrada de análise$IFS
não funcionará se você estiver procurando linhas em branco. Mas quem gostaria de selecionar em uma lista numerada de linhas em branco?Última observação: como isso funciona convertendo diretamente a entrada numérica do usuário nos parâmetros posicionais numéricos armazenados na matriz de argumentos da função, a saída será o que o usuário selecionar, quantas vezes o usuário selecionar e na ordem em que o usuário selecionar. isto.
Por exemplo:
fonte