Siempre dudo mucho en jugar $IFSporque 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 $IFSotra variable y luego restaurarlo inmediatamente después de que termine de usar $IFSpara algo.
¿Es esto práctico? ¿O es esencialmente inútil y debería IFSvolver directamente a lo que sea necesario para sus usos posteriores?
bash
shell-script
Steven Lu
fuente
fuente

$' \t\n'si está usando bash.unset $IFSsimplemente 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/shellshebang). 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
IFSvalor 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.reads 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 IFShace 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 IFSno 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 -ual proporcionar un valor predeterminado cuando se desarma (ese valor es la cadena vacía en este caso) .La
IFSmodificació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
echohecho, se llamará al comando con suIFSvariable establecida en,, peroechono le importa ni lo usaIFS. La magia de expandirse"${array[*]}"a una cadena la realiza el (sub) shell antes deechoque incluso se invoque.Para leer en un archivo completo (que no contiene
NULLbytes) 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: siIFSno está configurado, el comportamiento de todas las funcionalidades de bash que usa internamenteIFSes exactamente el mismo que siIFStuviera el valor predeterminado de$' \t\n'.Ajuste
IFSen 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 unNULLbyte, en lugar de la nueva línea habitual.Para dividir a lo
$PATHlargo 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
.csvarchivo 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$PATHy 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 globalIFSfuncionará 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:commandes 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_IFSvariable global para guardar / restaurar, obtiene un error.IFSestaba 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 globalIFSo 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
IFSEsto 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
IFStener ningún valor en particular (ni siquiera el predeterminado) o incluso establecerlo. En su lugar, debe establecer explícitamenteIFScualquier fragmento cuyo comportamiento dependaIFS.Si
IFSse 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 queIFSimporta 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
IFStodos modos?Afortunadamente, no hay tantos escenarios en los que
IFSimporta (suponiendo que siempre cites tus expansiones ):"$*"y"${array[*]}"expansionesreadorientación integrada de múltiples variables (read VAR1 VAR2 VAR3) o una variable de matriz (read -a ARRAY_VAR_NAME)readsegmentació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$PATHsólo búsquedas de:en una cadena C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATHejemplo 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
IFSinicialmente no se configuró.