¿Cuándo puedo usar un IFS temporal para la división de campos?

19

En bash, di que tienes var=a.b.c., entonces:

$ IFS=. printf "%s\n" $var
a.b.c

Sin embargo, dicho uso IFStiene efecto al crear una matriz:

$ IFS=. arr=($var)
$ printf "%s\n" "${arr[@]}"
a
b
c

Esto es muy conveniente, claro, pero ¿dónde está documentado? Una lectura rápida de las secciones sobre matrices o división de palabras en la documentación de Bash no da ninguna indicación de ninguna manera. Una búsqueda a IFStravés de la documentación de una sola página tampoco proporciona ninguna pista sobre este efecto.

No estoy seguro de cuándo puedo hacer de manera confiable:

IFS=x do something

Y espera que IFSeso afecte la división del campo.

muru
fuente

Respuestas:

28

La idea básica es que los VAR=VALUE some-commandconjuntos VARa VALUEla ejecución de some-commandcuando some-commandes un comando externo, y que no puede ser más elegante que eso. Si combina esta intuición con algún conocimiento de cómo funciona un shell, debe encontrar la respuesta correcta en la mayoría de los casos. La referencia POSIX es "Comandos simples" en el capítulo "Lenguaje de comandos de Shell" .

Si some-commandes un comando externo , VAR=VALUE some-commandes equivalente a env VAR=VALUE some-command. VARse exporta en el entorno de some-command, y su valor (o falta de valor) en el shell no cambia.

Si some-commandes una función , entonces VAR=VALUE some-commandes equivalente a VAR=VALUE; some-command, es decir, la asignación permanece en su lugar después de que la función ha regresado y la variable no se exporta al entorno. La razón de esto tiene que ver con el diseño del shell Bourne (y posteriormente con la compatibilidad con versiones anteriores): no tenía facilidad para guardar y restaurar valores variables en torno a la ejecución de una función. No exportar la variable tiene sentido ya que una función se ejecuta en el propio shell. Sin embargo, ksh (incluidos ATT ksh93 y pdksh / mksh), bash y zsh implementan el comportamiento más útil donde VARse establece solo durante la ejecución de la función (también se exporta). En ksh , esto se hace si la función se define con la sintaxis kshfunction NAME …, no si se define con la sintaxis estándar NAME (). En bash , esto se hace solo en modo bash, no en modo POSIX (cuando se ejecuta con POSIXLY_CORRECT=1). En zsh , esto se hace si la posix_builtinsopción no está configurada; esta opción no está configurada de manera predeterminada pero está activada por emulate sho emulate ksh.

Si se some-commandtrata de una construcción, el comportamiento depende del tipo de construcción. Los componentes especiales especiales se comportan como funciones. Las incorporaciones especiales son las que deben implementarse dentro del shell porque afectan el shell de estado (por ejemplo break, cdafecta el flujo de control, afecta el directorio actual, setafecta los parámetros de posición y las opciones ...). Otras funciones incorporadas están integradas solo por rendimiento y conveniencia (principalmente, por ejemplo, la función bash printf -vsolo puede implementarse mediante una función integrada), y se comportan como un comando externo.

La asignación se lleva a cabo después de la expansión del alias, por lo que si some-commandes un alias , amplíelo primero para encontrar qué sucede.

Tenga en cuenta que en todos los casos, la asignación se realiza después de analizar la línea de comando, incluida cualquier sustitución de variable en la línea de comando. Entonces var=a; var=b echo $varimprime a, porque $varse evalúa antes de que se realice la asignación. Y así IFS=. printf "%s\n" $varusa el IFSvalor anterior para dividir $var.

He cubierto todos los tipos de comandos, pero hay un caso más: cuando no hay un comando para ejecutar , es decir, si el comando consiste solo en asignaciones (y posiblemente redireccionamientos). En ese caso, la asignación permanece en su lugar . VAR=VALUE OTHERVAR=OTHERVALUEes equivalente a VAR=VALUE; OTHERVAR=OTHERVALUE. Entonces IFS=. arr=($var), después , IFSpermanece establecido en .. Como puede usar $IFSen la asignación arrcon la expectativa de que ya tiene su nuevo valor, tiene sentido que el nuevo valor de IFSse use para la expansión de $var.

En resumen, puede usar solo IFSpara la división temporal de campos:

  • iniciando un nuevo shell o un subshell (por ejemplo, third=$(IFS=.; set -f; set -- $var; echo "$3")es una forma complicada de hacerlo, third=${var#*.*.}excepto que se comportan de manera diferente cuando el valor de varcontiene menos de dos .caracteres);
  • en ksh, con IFS=. some-functiondonde some-functionse define con la sintaxis ksh function some-function …;
  • en bash y zsh, IFS=. some-functionsiempre y cuando estén operando en modo nativo en oposición al modo de compatibilidad.
Gilles 'SO- deja de ser malvado'
fuente
" IFSpermanece establecido en ." Eek. Después de leer la primera parte, eso tiene sentido, pero antes de publicar esta Q, no hubiera esperado eso.
muru
1
Esta es una respuesta a una pregunta diferente
schily
Alguna explicación adicional en esta respuesta de hace algunos años .
Ti Strga
6

La respuesta de @Gilles es realmente genial, explica (en detalle) un tema complejo.

Sin embargo, creo que la respuesta a por qué este comando:

$ IFS=. printf "%s\n" $var
a.b.c

funciona como lo hace la simple idea de que toda la línea de comando se analiza antes de ejecutarse. Y que cada "palabra" es procesada una vez por el shell.
Las tareas, como IFS=., se retrasan (el paso 4 es el último):

4.- Cada asignación variable se ampliará ...

hasta justo antes de que se ejecute el comando y todas las expansiones en los argumentos se procesen primero para construir esta línea ejecutable:

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c

El valor de $varse expande con el IFS "anterior" a.b.cantes de que el comando printfreciba los argumentos "%s\n"y a.b.c.

Eval

Un nivel de retraso puede ser introducido por eval:

$ IFS=. eval printf "'%s\n'" \$var
a
b
c

La línea se analiza (primera vez) y 'IFS =.' se establece en el entorno como este:

$ printf '%s\n' $var

Luego se analiza nuevamente esto:

$ printf '%s\n' a b c

Y ejecutado a esto:

a
b
c

El valor de $var(abc) se divide por el valor de IFS en uso: ..

Ambiente

La parte compleja y complicada es lo que es válido en el entorno cuando !!!

Eso se explica muy bien en la primera parte de la respuesta de Gilles.

Con un detalle adicional.

Cuando se ejecuta este comando:

$ IFS=. arr=($var)

El valor de IFS se conserva en el entorno actual, sí:

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 

IFS para una sola declaración.

Pero podría evitarse: configurar IFS para una sola declaración

$ IFS=. command eval arr\=\(\$var\)

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 
Comunidad
fuente
2

Su pregunta sobre

var=a.b.c
IFS=. printf "%s\n" $var

Es un caso de esquina.

Esto se debe a que macro expansionen el comando ocurre antes de queIFS=. se establezca la variable de shell .

En otras palabras: cuando $varse expande, el IFSvalor anterior está activo, luego IFSse establece en '.'.

astuto
fuente