Siempre dudo mucho en jugar $IFS
porque es un golpe global.
Pero a menudo hace que cargar cadenas en una matriz bash sea agradable y conciso, y para las secuencias de comandos bash, la concisión es difícil de encontrar.
Así que me imagino que podría ser mejor que nada si trato de "guardar" el contenido inicial de $IFS
otra variable y luego restaurarlo inmediatamente después de que termine de usar $IFS
para algo.
¿Es esto práctico? ¿O es esencialmente inútil y debería IFS
volver directamente a lo que sea necesario para sus usos posteriores?
bash
shell-script
Steven Lu
fuente
fuente
$' \t\n'
si está usando bash.unset $IFS
simplemente no siempre lo restaura a lo que esperarías que sea el predeterminado.Respuestas:
Puede guardar y asignar a IFS según sea necesario. No hay nada de malo en hacerlo. No es raro guardar su valor para la restauración posterior a una modificación rápida y temporal, como su ejemplo de asignación de matriz.
Como @llua menciona en su comentario a su pregunta, simplemente deshabilitar IFS restaurará el comportamiento predeterminado, equivalente a asignar una nueva línea de tabulación de espacio.
Vale la pena considerar cómo puede ser más problemático no establecer / deshabilitar explícitamente IFS de lo que es hacerlo.
Desde la edición POSIX 2013, 2.5.3 Variables de Shell :
Un shell invocado compatible con POSIX puede o no heredar IFS de su entorno. De esto sigue:
"$*"
), pero que puede ejecutarse bajo un shell que inicializa IFS desde el entorno, debe establecer / deshabilitar explícitamente IFS para defenderse de la intrusión ambiental.Nota: es importante comprender que para esta discusión la palabra "invocado" tiene un significado particular. Un shell se invoca solo cuando se llama explícitamente con su nombre (incluido un
#!/path/to/shell
shebang). Un subshell, como podría ser creado por$(...)
ocmd1 || cmd2 &
, no es un shell invocado, y su IFS (junto con la mayoría de su entorno de ejecución) es idéntico al de su padre. Un shell invocado establece el valor de$
su pid, mientras que los subshells lo heredan.Esto no es simplemente una disquisición pedante; Existe una divergencia real en esta área. Aquí hay un breve script que prueba el escenario utilizando varios shells diferentes. Exporta un IFS modificado (establecido en
:
) a un shell invocado que luego imprime su IFS predeterminado.IFS generalmente no está marcado para exportación, pero, si lo fuera, tenga en cuenta cómo bash, ksh93 y mksh ignoran su entorno
IFS=:
, mientras que dash y busybox lo respetan.Alguna información de la versión:
Aunque bash, ksh93 y mksh no inicializan IFS desde el entorno, reexportan sus IFS modificados.
Si por alguna razón necesita pasar IFS de forma portátil a través del entorno, no puede hacerlo utilizando el propio IFS; necesitará asignar el valor a una variable diferente y marcar esa variable para exportar. Luego, los niños deberán asignar explícitamente ese valor a su IFS.
fuente
IFS
valor en la mayoría de las situaciones en las que se va a utilizar, por lo que a menudo no es terriblemente productivo incluso intentar "preservar" su valor original.read
s o referencias con comillas dobles$*
. Esa lista está justo en la parte superior de mi cabeza, por lo que puede no ser completa (especialmente cuando se consideran las extensiones POSIX de los proyectiles modernos).En general, es una buena práctica restablecer las condiciones por defecto.
Sin embargo, en este caso, no tanto.
¿Por qué?:
$' \t\n'
.unset IFS
hace que actúe como si estuviera configurado por defecto .Además, almacenar el valor IFS tiene un problema.
Si el IFS original no estaba configurado, el código
IFS="$OldIFS"
establecerá IFS en""
, no lo desarmará.Para mantener realmente el valor de IFS (incluso si no está configurado), use esto:
fuente
bash
,unset IFS
no puede deshabilitar IFS si se ha declarado local en un contexto primario (contexto de función) y no en el contexto actual.Tienes razón al dudar acerca de golpear a un global. No temas, es posible escribir un código de trabajo limpio sin modificar nunca el global real
IFS
, o hacer un baile de recuperación / restauración engorroso y propenso a errores.Usted puede:
establecer IFS para una sola invocación:
o
establecer IFS dentro de una subshell:
Ejemplos
Para obtener una cadena delimitada por comas de una matriz:
Nota: El
-
solo es para proteger una matriz vacíaset -u
al proporcionar un valor predeterminado cuando se desarma (ese valor es la cadena vacía en este caso) .La
IFS
modificación solo es aplicable dentro de la subshell generada por$()
sustitución del comando . Esto se debe a que los subshells tienen copias de las variables del shell de invocación y, por lo tanto, pueden leer sus valores, pero cualquier modificación realizada por el subshell solo afecta la copia del subshell y no la variable del padre.También podría estar pensando: ¿por qué no omitir la subshell y simplemente hacer esto?
Aquí no hay invocación de comandos, y esta línea se interpreta como dos asignaciones de variables posteriores independientes, como si fuera:
Finalmente, expliquemos por qué esta variante no funcionará:
De
echo
hecho, se llamará al comando con suIFS
variable establecida en,
, peroecho
no le importa ni lo usaIFS
. La magia de expandirse"${array[*]}"
a una cadena la realiza el (sub) shell antes deecho
que incluso se invoque.Para leer en un archivo completo (que no contiene
NULL
bytes) en una sola variable llamadaVAR
:Nota:
IFS=
es lo mismo queIFS=""
yIFS=''
, todo lo cual establece IFS en la cadena vacía, que es muy diferente deunset IFS
: siIFS
no está configurado, el comportamiento de todas las funcionalidades de bash que usa internamenteIFS
es exactamente el mismo que siIFS
tuviera el valor predeterminado de$' \t\n'
.Ajuste
IFS
en la cadena vacía garantiza que se conservan los espacios en blanco iniciales y finales.La lectura
-d ''
o-d ""
le dice que solo detenga su invocación actual en unNULL
byte, en lugar de la nueva línea habitual.Para dividir a lo
$PATH
largo de sus:
delimitadores:Este ejemplo es puramente ilustrativo. En el caso general en el que se divide a lo largo de un delimitador, es posible que los campos individuales contengan (una versión escapada de) ese delimitador. Piense en tratar de leer una fila de
.csv
archivo cuyas columnas pueden contener comas (escapadas o citadas de alguna manera). El fragmento anterior no funcionará según lo previsto para tales casos.Dicho esto, es poco probable que encuentres tales
:
caminos de contención dentro$PATH
. Si bien los nombres de ruta UNIX / Linux pueden contener un:
, parece que bash no podría manejar tales rutas de todos modos si intenta agregarlos a su$PATH
y almacenar archivos ejecutables en ellos, ya que no hay código para analizar dos puntos escapados / entre comillas : código fuente de bash 4.4 .Finalmente, tenga en cuenta que el fragmento agrega una nueva línea final al último elemento de la matriz resultante (como lo llamó @ StéphaneChazelas en los comentarios ahora eliminados), y que si la entrada es la cadena vacía, la salida será un elemento único matriz, donde el elemento consistirá en una nueva línea (
$'\n'
).Motivación
El
old_IFS="${IFS}"; command; IFS="${old_IFS}"
enfoque básico que toca lo globalIFS
funcionará como se espera para los scripts más simples. Sin embargo, tan pronto como agregue cualquier complejidad, puede separarse fácilmente y causar problemas sutiles:command
es una función bash que también modifica el globalIFS
(ya sea directamente o, oculto a la vista, dentro de otra función que llama), y mientras lo hace por error usa la mismaold_IFS
variable global para guardar / restaurar, obtiene un error.IFS
estaba desarmado, el ingenuo guardar y restaurar no funcionará, e incluso resultará en fallas absolutas si la opción de shell comúnmente (mal utilizada)set -u
(akaset -o nounset
) Está en vigor.help trap
). Si ese código también modifica el globalIFS
o supone que tiene un valor particular, puede obtener errores sutiles.Podría idear una secuencia de salvar / restaurar más robusta (como la propuesta en esta otra respuesta para evitar algunos o todos estos problemas. Sin embargo, tendría que repetir esa pieza de código repetitivo ruidoso donde sea que necesite temporalmente una costumbre)
IFS
. reduce la legibilidad y la facilidad de mantenimiento del código.Consideraciones adicionales para scripts de biblioteca
IFS
Esto es especialmente preocupante para los autores de bibliotecas de funciones de shell que necesitan asegurarse de que su código funcione de manera sólida independientemente del estado global (IFS
, opciones de shell, ...) impuesto por sus invocadores, y también sin alterar ese estado en absoluto (los invocadores pueden confiar en él para permanecer siempre estático).Al escribir el código de la biblioteca, no puede confiar en
IFS
tener ningún valor en particular (ni siquiera el predeterminado) o incluso establecerlo. En su lugar, debe establecer explícitamenteIFS
cualquier fragmento cuyo comportamiento dependaIFS
.Si
IFS
se establece explícitamente en el valor necesario (incluso si ese es el predeterminado) en cada línea de código donde el valor importa utilizando cualquiera de los dos mecanismos descritos en esta respuesta es apropiado para localizar el efecto, entonces el código es ambos independiente del estado global y evita golpearlo por completo. Este enfoque tiene el beneficio adicional de hacerlo muy explícito para una persona que lee el script queIFS
importa precisamente para este comando / expansión a un costo textual mínimo (en comparación con incluso el guardado / restauración más básico).¿Qué código se ve afectado de
IFS
todos modos?Afortunadamente, no hay tantos escenarios en los que
IFS
importa (suponiendo que siempre cites tus expansiones ):"$*"
y"${array[*]}"
expansionesread
orientación integrada de múltiples variables (read VAR1 VAR2 VAR3
) o una variable de matriz (read -a ARRAY_VAR_NAME
)read
segmentación de una sola variable cuando se trata de espacios en blanco iniciales o finales o no espacios en blanco que aparecenIFS
.fuente
:
cuando:
es el delimitador?:
es un carácter válido para usar en un nombre de archivo en la mayoría de los sistemas de archivos UNIX / Linux, por lo que es completamente posible tener un directorio con un nombre que lo contenga:
. Tal vez algunas conchas tienen una disposición para escapar:
en el PATH utilizando algo como\:
, y luego se verían columnas apareciendo que no son delimitadores reales (Parece fiesta no permite tales escape. La función de bajo nivel utilizado cuando la iteración en$PATH
sólo búsquedas de:
en una cadena C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
ejemplo de división sea:
más claro.¿Por qué arriesgarse a que un error tipográfico establezca IFS
$' \t\n'
cuando todo lo que tiene que hacer esAlternativamente, puede llamar a una subshell si no necesita ninguna variable establecida / modificada dentro de:
fuente
IFS
inicialmente no se configuró.