Problema general
Quiero escribir un script que interactúe con el usuario a pesar de que está en medio de una cadena de tuberías.
Ejemplo concreto
Concretamente, toma una fileo stdin, muestra líneas (con números de línea), le pide al usuario que ingrese una selección o números de línea, y luego imprime las líneas correspondientes en stdout. Llamemos a este script selector. Entonces, básicamente, quiero poder hacer
grep abc foo | selector > myfile.tmp
Si foocontiene
blabcbla
foo abc bar
quux
xyzzy abc
luego selectorme presenta (¡en la terminal, no dentro myfile.tmp!) con opciones
1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:
después de lo cual escribo
2-3
y terminar con
foo abc bar
xyzzy abc
como contenido de myfile.tmp.
Tengo un script selector en funcionamiento, y básicamente funciona perfectamente si no redirijo la entrada y la salida. Entonces
selector foo
se comporta como yo quiero. Sin embargo, al unir elementos como en el ejemplo anterior, selectorimprime las opciones presentadas myfile.tmpe intenta leer una selección de la entrada grepped.
Mi acercamiento
He tratado de usar la -ubandera de read, como en
exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "
Pero esto no hace lo que esperaba.
P: ¿Cómo obtengo la interacción real del usuario?

cmd | { some processing; read var </dev/tty; } | cmdalias 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 bastante bien. Sin embargo segrep b foo | selector | wc -lrompe por aquí. ¿Hay alguna idea de cómo arreglar eso? Por cierto, elrangeselectque usé se puede encontrar en pastebin.com/VAxTSSHs . Es un script AWK simple que imprime las líneas de un archivo correspondiente a un rango dado de números de lino. (Los rangos pueden ser cosas como "3-10, 12,14,16-20".)aliaseso, más bienselector() { all of that stuff...; }en una función.aliases renombrar comandos simples mientras que las funciones empaquetan un comando compuesto en un solo comando simple .Respuestas:
El uso
/proc/$PPID/fd/0no es confiable: el padre delselectorproceso puede no tener el terminal como entrada.Hay una ruta estándar que siempre se refiere a la terminal del proceso actual:
/dev/tty.o
fuente
He escrito una pequeña función: no responderá sobre lo que ha pedido encadenamiento de tubería, pero resolverá su problema.
La función entrega todos los argumentos a los que se los da de inmediato
grep. Si usa un glob de shell para especificar los archivos que debería leer, devolverá todas las coincidencias en todos los archivos, comenzando con el primero en el orden glob y terminando con la última coincidencia.greppasa su salida anlqué números cada línea y cuál pasa su salida a lateecual duplica su salida tanto astdoutcomo a/dev/tty. Esto significa que la salida de la tubería se imprime simultáneamente tanto en la matriz de argumentos de la función donde se divide en\nlíneas electrónicas como en el terminal a medida que funciona.Luego, la
_in()función intentareaden una selección si hay al menos 1 resultado de la acción anterior un máximo de cinco veces. La selección puede consistir solo en números separados por espacios, o bien rangos de números separados por-. Si hay algo másread(incluida una línea en blanco) volverá a intentarlo, pero solo, como antes, un máximo de cinco veces.Por último, la
_out()función analiza la selección del usuario y expande cualquier rango en ella. Imprime sus resultados en el formulario"${[num]}"para cada uno, igualando así el valor de las líneas almacenadas eninf()la matriz arg de. Esta salida seevaledita como argumentos en losprintfque, por lo tanto, imprime solo las líneas que el usuario ha seleccionado.Es explícitamente
readdesde la terminal y solo imprime elSelect:menústderry, por lo tanto, es mucho más fácil de usar. Por ejemplo, lo siguiente funciona:Pero puede usar cualquier opción que le dé
grepy cualquier cantidad de nombres de archivo que pueda darle también. Es decir, puede usar cualquier tipo, ya que como efecto secundario de su entrada de análisis$IFSno funcionará si está buscando líneas en blanco. Pero, ¿quién querría seleccionar de una lista numerada de líneas en blanco?Última nota que debido a que esto funciona traduciendo directamente la entrada numérica del usuario en los parámetros posicionales numéricos almacenados en la matriz de argumentos de la función, la salida será lo que el usuario seleccione, tantas veces como el usuario lo seleccione, y en el orden que el usuario seleccione eso.
Por ejemplo:
fuente