IFS и подстановка команд - PullRequest
       32

IFS и подстановка команд

0 голосов
/ 08 сентября 2018

Я пишу сценарий оболочки для чтения входных CSV-файлов и запуска программы Java соответственно.

#!/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

CSV-файл:

a|b|c

Интересно, что $ myScript вне цикла for работает, но $ myScript внутри цикла for говорит: "/ usr / bin / java -version: not found [Нет такого файла или каталога]". Я узнал, что это потому, что я устанавливаю IFS. Если я прокомментирую IFS и изменим файл csv на

a b c

Это работает! Я представляю оболочку, использующую IFS по умолчанию для разделения команды / usr / bin / java, а затем применяю аргумент -version позже. Поскольку я изменил IFS, он воспринимает всю строку как одну команду - или, как мне кажется, это происходит.

Но это мое требование: у меня есть файл csv с пользовательским разделителем, и в команде есть аргументы, разделенные пробелом. Как я могу сделать это правильно?

Ответы [ 4 ]

0 голосов
/ 08 сентября 2018

Самое простое желание - избежать изменения IFS и выполнить разбиение с помощью read -d <delimiter> следующим образом:

#!/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 голосов
/ 08 сентября 2018

IFS должен быть помещен позади "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
0 голосов
/ 08 сентября 2018

IFS указывает, как разделить значения переменных в кавычках.Это относится как к $row, так и к $myscript.

. Если вы хотите использовать IFS для разделения, что удобно для простого sh, вам нужно изменить значение IFS илидоговориться о необходимости того же значения.В этом конкретном случае вы можете легко организовать нужное значение, определив myScript как myScript="/usr/bin/java|-version".Кроме того, вы можете изменить значение IFS как раз вовремя.В обоих случаях обратите внимание, что подстановка без кавычек не просто разделяет значение с помощью IFS, она также интерпретирует каждую часть как шаблон с подстановочными знаками и заменяет его списком совпадающих имен файлов, если они есть.Это означает, что если ваш CSV-файл содержит строку типа

foo|*|bar

, тогда строка будет не foo, *, bar, а foo, каждое имя файла в текущем каталогеbar.Для обработки данных, как это, вам нужно отключить с set -f.Также помните, что read читает строки продолжения, когда строка заканчивается обратной косой чертой, и убирает начальные и конечные символы IFS.Используйте IFS= read -r, чтобы отключить эти два поведения.

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

Однако существуют более эффективные способы, позволяющие полностью избежать IFS-расщепления.Не храните команду в строке, разделенной пробелами: в сложных случаях происходит сбой, например, команды, для которых требуется аргумент, содержащий пробел.Существует три надежных способа сохранить команду:

  • Сохранить команду в функции.Это самый естественный подход.Выполнение команды - это код;Вы определяете код в функции.Вы можете сослаться на аргументы функции вместе как "$@".

    myScript () {
        /usr/bin/java -version "$@"
    }
    …
    myScript extra_argument_1 extra_argument_2
    
  • Сохранить имя исполняемой команды и ее аргументы в массиве.

    myScript=(/usr/bin/java -version)
    …
    "${myScript[@]}" extra_argument_1 extra_argument_2
    
  • Сохраните команду оболочки , т.е. что-то, что должно быть проанализировано оболочкой.Чтобы оценить код оболочки в строке, используйте eval.Обязательно заключите аргумент в кавычки, как и любое другое расширение переменной, чтобы избежать преждевременного раскрытия подстановочного знакаЭтот подход более сложный, так как требует тщательного цитирования.Это действительно полезно, когда вам нужно сохранить команду в строке, например, потому что она входит в качестве параметра в ваш скрипт.Обратите внимание, что вы не можете разумно передавать дополнительные аргументы таким образом.

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

Кроме того, поскольку вы используете ksh, а не обычный sh, вам не нужно использовать IFS разделить строку ввода.Вместо этого используйте read -A для непосредственного разделения на массив.

#!/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"
0 голосов
/ 08 сентября 2018

IFS сообщает оболочке, какие символы разделяют «слова», то есть различные компоненты команды. Поэтому, когда вы удаляете символ пробела из IFS и запускаете foo bar, сценарий видит единственный аргумент"foo bar" вместо "foo" и "bar".

...