Cómo recopilar correctamente una matriz de líneas en zsh


42

Pensé que lo siguiente agruparía la salida de my_commanden una matriz de líneas:

IFS='\n' array_of_lines=$(my_command);

entonces eso $array_of_lines[1]se referiría a la primera línea en la salida de my_command, $array_of_lines[2]a la segunda, y así sucesivamente.

Sin embargo, el comando anterior no parece funcionar bien. Parece que también divide la salida de my_commandalrededor del personaje n, como lo he comprobado print -l $array_of_lines, con lo que creo imprime elementos de una matriz línea por línea. También he comprobado esto con:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

En un segundo intento, pensé que agregar evalpodría ayudar:

IFS='\n' array_of_lines=$(eval my_command);

pero obtuve exactamente el mismo resultado que sin él.

Finalmente, siguiendo la respuesta en Lista de elementos con espacios en zsh , también he intentado usar banderas de expansión de parámetros en lugar de IFSdecirle a zsh cómo dividir la entrada y recopilar los elementos en una matriz, es decir:

array_of_lines=("${(@f)$(my_command)}");

Pero todavía obtuve el mismo resultado (división en curso n)

Con esto, tengo las siguientes preguntas:

Q1. ¿Cuáles son las formas "adecuadas" de recopilar la salida de un comando en una matriz de líneas?

Q2 ¿Cómo puedo especificar IFSdividir solo en líneas nuevas?

Q3. Si uso banderas de expansión de parámetros como en mi tercer intento anterior (es decir, usando @f) para especificar la división, ¿zsh ignora el valor de IFS? ¿Por qué no funcionó arriba?

Respuestas:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

Primer error (→ Q2): se IFS='\n'establece IFSen los dos caracteres \y n. Para establecer IFSuna nueva línea, use IFS=$'\n'.

Segundo error: para establecer una variable a un valor de matriz, es necesario paréntesis alrededor de los elementos: array_of_lines=(foo bar).

Esto funcionaría, excepto que elimina las líneas vacías, porque los espacios en blanco consecutivos cuentan como un separador único:

IFS=$'\n' array_of_lines=($(my_command))

Puede retener las líneas vacías excepto al final doblando el espacio en blanco en IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Para seguir también las líneas vacías, tendría que agregar algo a la salida del comando, porque esto sucede en la sustitución del comando en sí, no en analizarlo.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(suponiendo que la salida de my_commandno termine en una línea no delimitada; también tenga en cuenta que pierde el estado de salida de my_command)

Tenga en cuenta que todos los fragmentos anteriores se dejan IFScon su valor no predeterminado, por lo que pueden estropear el código posterior. Para mantener la configuración de IFSlocal, coloque todo en una función donde declare IFSlocal (aquí también se ocupa de preservar el estado de salida del comando):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Pero recomiendo no meterse con IFS; en su lugar, use el findicador de expansión para dividir en nuevas líneas (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

O para preservar las líneas vacías finales:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

El valor de IFSno importa allí. Sospecho que utilizó un comando que se divide IFSpara imprimir $array_of_linesen sus pruebas (→ Q3).


77
¡Esto es muy complicado! "${(@f)...}"es lo mismo que ${(f)"..."}, pero de una manera diferente. (@)dentro de comillas dobles significa "producir una palabra por elemento de matriz" y (f)significa "dividir en una matriz por nueva línea". PD: Enlace a los documentos
ovejas voladoras

3
@flyingsheep, no ${(f)"..."}omitiría líneas vacías, las "${(@f)...}"conserva. Esa es la misma distinción entre $argvy "$argv[@]". Esa "$@"cosa para preservar todos los elementos de una matriz proviene del shell Bourne a fines de los años 70.
Stéphane Chazelas

4

Dos problemas: primero, aparentemente las comillas dobles tampoco interpretan los escapes de barra invertida (perdón por eso :). Usa $'...'comillas. Y de acuerdo con man zshparam, para recopilar palabras en una matriz, debe encerrarlas entre paréntesis. Entonces esto funciona:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

No puedo responder a tu Q3. Espero nunca tener que saber cosas tan esotéricas :).


-2

También puede usar tr para reemplazar la nueva línea con espacio:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
¿Qué pasa si las líneas contienen espacios?
don_crissti

2
Eso no tiene sentido. Tanto SPC como NL están en el valor predeterminado de $IFS. Traducir uno al otro no hace ninguna diferencia.
Stéphane Chazelas

¿Eran razonables mis ediciones? No pude hacerlo funcionar como era
John P

(Tiempo de espera agotado) Admito que lo edité sin comprender realmente cuál era la intención, pero creo que la traducción es un buen comienzo para la separación basada en la manipulación de cadenas. No traduciría a espacios, y los dividiría en ellos, a menos que el comportamiento esperado fuera más parecido echo, donde las entradas son más o menos montones de palabras separadas por quién se preocupa.
John P
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.