Estoy escribiendo un script de shell para leer los archivos csv de entrada y ejecutar un programa java en consecuencia.

#!/usr/bin/ksh
CSV_FILE=${1}
myScript="/usr/bin/java -version"
while read row
do
     $myScript
     IFS=$"|"
     for column in $row
     do 
        $myScript
     done
done < $CSV_FILE

Archivo csv:

a|b|c

Curiosamente, $ myScript fuera del bucle for funciona pero el $ myScript dentro del bucle for dice "/ usr / bin / java -version: not found [No existe tal archivo o directorio]". He llegado a saber que es porque estoy configurando IFS. Si comento IFS, y cambio el archivo csv a

a b c

Funciona ! Me imagino que el shell usa el IFS predeterminado para separar el comando / usr / bin / java y luego aplicar el argumento -version más adelante. Desde que cambié el IFS, está tomando toda la cadena como un solo comando, o eso es lo que creo que está sucediendo.

Pero este es mi requisito: tengo un archivo csv con un delimitador personalizado, y el comando tiene argumentos, separados por espacio. ¿Cómo puedo hacer esto correctamente?

1
Mayavi 8 sep. 2018 a las 10:58

4 respuestas

La mejor respuesta

IFS indica cómo dividir los valores de las variables en sustituciones sin comillas. Se aplica tanto a $row como a $myscript.

Si desea utilizar IFS para dividir, lo cual es conveniente en sh simple, entonces debe cambiar el valor de IFS o hacer arreglos para necesitar el mismo valor. En este caso particular, puede organizar fácilmente para necesitar el mismo valor, definiendo myScript como myScript="/usr/bin/java|-version". Alternativamente, puede cambiar el valor de IFS justo a tiempo. En ambos casos, tenga en cuenta que una sustitución sin comillas no solo divide el valor con IFS, sino que interpreta cada parte como un patrón comodín y la reemplaza por la lista de nombres de archivos coincidentes, si los hay. Esto significa que si su archivo CSV contiene una línea como

foo|*|bar

Entonces la fila no será foo, *, bar sino foo, cada nombre de archivo en el directorio actual, bar. Para procesar los datos de esta manera, debe desactivar con set -f. Recuerde también que read lee las líneas de continuación cuando una línea termina con una barra diagonal inversa, y elimina los caracteres IFS iniciales y finales. Use IFS= read -r para desactivar estos dos comportamientos.

myScript="/usr/bin/java -version"
set -f
while IFS= read -r row
do
    $myScript
    IFS='|'
    for column in $row
    do 
        IFS=' '
        $myScript
    done
done

Sin embargo, hay mejores maneras de evitar la división de IFS por completo. No almacene un comando en una cadena separada por espacios: falla en casos complejos, como los comandos que necesitan un argumento que contenga un espacio. Hay tres formas robustas de almacenar un comando:

  • Almacene el comando en una función. Este es el enfoque más natural. Ejecutar un comando es código; Usted define el código en una función. Puede referirse a los argumentos de la función colectivamente como "$@".

    myScript () {
        /usr/bin/java -version "$@"
    }
    …
    myScript extra_argument_1 extra_argument_2
    
  • Almacene un nombre de comando ejecutable y sus argumentos en una matriz.

    myScript=(/usr/bin/java -version)
    …
    "${myScript[@]}" extra_argument_1 extra_argument_2
    
  • Almacene un comando de shell , es decir, algo que el shell debe analizar. Para evaluar el código de shell en una cadena, use eval. Asegúrese de citar el argumento, como cualquier otra expansión variable, para evitar la expansión prematura de comodines. Este enfoque es más complejo ya que requiere una cita cuidadosa. Solo es realmente útil cuando tiene que almacenar el comando en una cadena, por ejemplo, porque viene como un parámetro para su script. Tenga en cuenta que no puede pasar argumentos adicionales de esta manera.

    myScript='/usr/bin/java -version'
    …
    eval "$myScript"
    

Además, como está usando ksh y no sh simple, no necesita usar IFS para dividir la línea de entrada. Utilice read -A en su lugar para dividirse directamente en una matriz.

#!/usr/bin/ksh
CSV_FILE=${1}
myScript=(/usr/bin/java -version)
while IFS='|' read -r -A columns
do
    "${myScript[@]}"
    for column in "${columns[@]}"
    do 
        "${myScript[@]}"
    done
done <"$CSV_FILE"
1
Gilles 'SO- stop being evil' 8 sep. 2018 a las 09:39

El IFS debe colocarse detrás de "while"

#!/usr/bin/ksh
CSV_FILE=${1}
myScript="/usr/bin/java -version"
while IFS="|" read row
do
 $myScript
 for column in $row
 do 
    $myScript
 done
done < $CSV_FILE
-1
Ivan 8 sep. 2018 a las 09:35

La solución más simple es evitar cambiar IFS y dividir con read -d <delimiter> así:

#!/usr/bin/ksh
CSV_FILE=${1}
myScript="/usr/bin/java -version"
while read -A -d '|' columns
do
     $myScript
     for column in "${columns[@]}"
     do 
        echo next is "$column"
        $myScript
     done
done < $CSV_FILE
0
A.H. 8 sep. 2018 a las 09:48

IFS le dice al shell qué caracteres separan las "palabras", es decir, los diferentes componentes de un comando. Entonces, cuando elimina el carácter de espacio de IFS y ejecuta foo bar, el script ve un argumento único "foo bar" en lugar de "foo" y "bar".

0
l0b0 8 sep. 2018 a las 09:08