Si debe almacenar la salida y desea una matriz, mapfile
se lo pone fácil.
Primero, considere si necesita almacenar la salida de su comando. Si no lo hace, simplemente ejecute el comando.
Si decide que desea leer la salida de un comando como una matriz de líneas, es cierto que una forma de hacerlo es deshabilitar el globbing, configurar la IFS
división en líneas, usar la sustitución de comandos dentro de la (
)
sintaxis de creación de la matriz y restablecer IFS
después, que es más complejo de lo que parece. La respuesta de terdon cubre algo de ese enfoque. Pero le sugiero que utilice mapfile
, en su lugar , el comando incorporado del shell Bash para leer texto como una matriz de líneas. Aquí hay un caso simple en el que estás leyendo un archivo:
mapfile < filename
Esto lee líneas en una matriz llamada MAPFILE
. Sin la redirección de entrada , estaría leyendo desde la entrada estándar del shell (generalmente su terminal) en lugar del archivo nombrado por . Para leer en una matriz que no sea la predeterminada , pase su nombre. Por ejemplo, esto se lee en la matriz :< filename
filename
MAPFILE
lines
mapfile lines < filename
Otro comportamiento predeterminado que puede elegir cambiar es que los caracteres de nueva línea al final de las líneas se dejan en su lugar; aparecen como el último carácter en cada elemento de la matriz (a menos que la entrada finalice sin un carácter de nueva línea, en cuyo caso el último elemento no tiene uno). Para masticar estas nuevas líneas para que no aparezcan en la matriz, pase la -t
opción a mapfile
. Por ejemplo, esto se lee en la matriz records
y no escribe caracteres de nueva línea al final de sus elementos de la matriz:
mapfile -t records < filename
También puede usar -t
sin pasar un nombre de matriz; es decir, también funciona con un nombre de matriz implícito MAPFILE
.
El mapfile
shell integrado admite otras opciones y, alternativamente, se puede invocar como readarray
. Ejecute help mapfile
(y help readarray
) para más detalles.
Pero no desea leer desde un archivo, desea leer desde la salida de un comando. Para lograr eso, use la sustitución del proceso . Este comando lee líneas del comando some-command
con arguments...
sus argumentos de línea de comando y los coloca en mapfile
la matriz predeterminada MAPFILE
, con sus caracteres de línea nueva eliminados:
mapfile -t < <(some-command arguments...)
La sustitución del proceso reemplaza con un nombre de archivo real desde el cual se puede leer la salida de la ejecución . El archivo es una tubería con nombre en lugar de un archivo normal y, en Ubuntu, se llamará como (a veces con algún otro número que no sea ), pero no necesita preocuparse por los detalles, porque el shell se encarga de eso todo detrás de escena<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Puede pensar que podría renunciar a la sustitución de procesos utilizando en su lugar, pero eso no funcionará porque, cuando tiene una tubería de múltiples comandos separados por , Bash ejecuta ejecuta todos los comandos en subcapas . Por lo tanto, tanto y ejecutar en sus propios entornos , pero inicializados de separar del medio ambiente de la concha en la que se ejecuta la tubería. En el subnivel donde se ejecuta, el conjunto que hace son rellenados, pero luego esa matriz se descarta cuando termina el comando. nunca se crea o modifica para la persona que llama.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Así es como se ve el ejemplo en la respuesta de terdon , en su totalidad, si usa mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Eso es. No necesita verificar si IFS
se configuró , realizar un seguimiento de si no se configuró y con qué valor, configurarlo en una nueva línea, luego restablecerlo o reiniciarlo más tarde. Usted no tiene que desactivar el englobamiento (por ejemplo, con set -f
) - que es realmente necesario si va a utilizar ese método en serio, ya que los nombres de archivo pueden contener *
, ?
y [
--¡entonces volver a habilitar ( set +f
) después.
También puede reemplazar ese ciclo en particular por completo con un solo printf
comando, aunque esto no es realmente un beneficio mapfile
, ya que puede hacerlo si usa mapfile
u otro método para completar la matriz. Aquí hay una versión más corta:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Es importante tener en cuenta que, como menciona Terdon , operar línea por línea no siempre es apropiado y no funcionará correctamente con nombres de archivo que contengan nuevas líneas. Recomiendo no nombrar archivos de esa manera, pero puede suceder, incluso por accidente.
No existe realmente una solución única para todos.
Usted solicitó "un comando genérico para todos los escenarios", y el enfoque de utilizar mapfile
algo se acerca a ese objetivo, pero le insto a que reconsidere sus requisitos.
La tarea que se muestra arriba se logra mejor con solo un find
comando:
find . -type d -printf 'DIR: %p\n'
También puede usar un comando externo como sed
agregar DIR:
al comienzo de cada línea. Esto podría decirse que es algo feo, y a diferencia de ese comando find, agregará "prefijos" adicionales dentro de los nombres de archivo que contienen líneas nuevas, pero funciona independientemente de su entrada, por lo que cumple con sus requisitos y aún es preferible leer la salida en un variable o matriz :
find . -type d | sed 's/^/DIR: /'
Si necesita enumerar y también realizar una acción en cada directorio encontrado, como ejecutar some-command
y pasar la ruta del directorio como argumento, también find
puede hacerlo:
find . -type d -print -exec some-command {} \;
Como otro ejemplo, volvamos a la tarea general de agregar un prefijo a cada línea. Supongamos que quiero ver la salida de help mapfile
pero numerar las líneas. Realmente no lo usaría mapfile
para esto, ni ningún otro método que lo lea en una variable de shell o matriz de shell. Suponiendo help mapfile | cat -n
que no da el formato que quiero, puedo usar awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Leer toda la salida de un comando en una sola variable o matriz a veces es útil y apropiado, pero tiene grandes desventajas. No solo tiene que lidiar con la complejidad adicional de usar su shell cuando un comando existente o una combinación de comandos existentes ya pueden hacer lo que necesita también o mejor, sino que toda la salida del comando debe almacenarse en la memoria. A veces puede saber que no es un problema, pero a veces puede estar procesando un archivo grande.
Una alternativa que se intenta comúnmente, y a veces se hace bien, es leer la entrada línea por línea con read -r
un bucle. Si no necesita almacenar líneas anteriores mientras opera en líneas posteriores, y tiene que usar una entrada larga, entonces puede ser mejor que mapfile
. Pero, también, debe evitarse en los casos en que simplemente puede canalizarlo a un comando que puede hacer el trabajo, que es la mayoría de los casos .