IFS 和命令替换
IFS and command substitution
我正在编写一个 shell 脚本来读取输入的 csv 文件和 运行 一个 java 相应的程序。
#!/usr/bin/ksh
CSV_FILE=
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
有趣的是,for 循环外的 $myScript 有效,但 for 循环内的 $myScript 显示“/usr/bin/java -version: not found [No such file or directory]”。我已经知道这是因为我正在设置 IFS。如果我评论 IFS,并将 csv 文件更改为
a b c
有效!我想象 shell 使用默认 IFS 来分隔命令 /usr/bin/java ,然后稍后应用 -version 参数。由于我更改了 IFS,它将整个字符串作为一个命令 - 或者这就是我认为正在发生的事情。
但这是我的要求:我有一个带有自定义分隔符的 csv 文件,命令中有参数,由 space 分隔。我怎样才能正确地做到这一点?
IFS
告诉 shell 哪些字符分隔 "words",即一个命令的不同组成部分。因此,当您从 IFS
和 运行 foo bar
中删除 space 字符时,脚本会看到 单个参数 "foo bar"而不是 "foo" 和 "bar".
IFS 应该放在 "while"
后面
#!/usr/bin/ksh
CSV_FILE=
myScript="/usr/bin/java -version"
while IFS="|" read row
do
$myScript
for column in $row
do
$myScript
done
done < $CSV_FILE
IFS
指示如何拆分未引用替换中的变量值。它适用于 $row
和 $myscript
。
如果你想使用IFS
做拆分,这在plain 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 分裂。不要将命令存储在 space 分隔的字符串中:它在复杂情况下会失败,例如需要包含 space 的参数的命令。存储命令的三种可靠方式:
将命令存储在函数中。这是最自然的做法。 运行一个命令就是代码;您在函数中定义代码。您可以将函数的参数统称为 "$@"
.
myScript () {
/usr/bin/java -version "$@"
}
…
myScript extra_argument_1 extra_argument_2
将可执行命令名称及其参数存储在一个数组中。
myScript=(/usr/bin/java -version)
…
"${myScript[@]}" extra_argument_1 extra_argument_2
存储一个shell命令,即要被shell解析的东西。要计算字符串中的 shell 代码,请使用 eval
。一定要像任何其他变量扩展一样引用参数,以避免过早的通配符扩展。这种方法更复杂,因为它需要仔细引用。它仅在必须将命令存储在字符串中时才真正有用,例如因为它作为脚本的参数出现。请注意,您不能以这种方式明智地传递额外的参数。
myScript='/usr/bin/java -version'
…
eval "$myScript"
此外,由于您使用的是 ksh 而不是普通的 sh,因此您不需要使用 IFS
来拆分输入行。使用 read -A
直接分割成一个数组。
#!/usr/bin/ksh
CSV_FILE=
myScript=(/usr/bin/java -version)
while IFS='|' read -r -A columns
do
"${myScript[@]}"
for column in "${columns[@]}"
do
"${myScript[@]}"
done
done <"$CSV_FILE"
最简单的解决方案是避免更改 IFS
并像这样使用 read -d <delimiter>
进行拆分:
#!/usr/bin/ksh
CSV_FILE=
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
我正在编写一个 shell 脚本来读取输入的 csv 文件和 运行 一个 java 相应的程序。
#!/usr/bin/ksh
CSV_FILE=
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
有趣的是,for 循环外的 $myScript 有效,但 for 循环内的 $myScript 显示“/usr/bin/java -version: not found [No such file or directory]”。我已经知道这是因为我正在设置 IFS。如果我评论 IFS,并将 csv 文件更改为
a b c
有效!我想象 shell 使用默认 IFS 来分隔命令 /usr/bin/java ,然后稍后应用 -version 参数。由于我更改了 IFS,它将整个字符串作为一个命令 - 或者这就是我认为正在发生的事情。
但这是我的要求:我有一个带有自定义分隔符的 csv 文件,命令中有参数,由 space 分隔。我怎样才能正确地做到这一点?
IFS
告诉 shell 哪些字符分隔 "words",即一个命令的不同组成部分。因此,当您从 IFS
和 运行 foo bar
中删除 space 字符时,脚本会看到 单个参数 "foo bar"而不是 "foo" 和 "bar".
IFS 应该放在 "while"
后面#!/usr/bin/ksh
CSV_FILE=
myScript="/usr/bin/java -version"
while IFS="|" read row
do
$myScript
for column in $row
do
$myScript
done
done < $CSV_FILE
IFS
指示如何拆分未引用替换中的变量值。它适用于 $row
和 $myscript
。
如果你想使用IFS
做拆分,这在plain 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 分裂。不要将命令存储在 space 分隔的字符串中:它在复杂情况下会失败,例如需要包含 space 的参数的命令。存储命令的三种可靠方式:
将命令存储在函数中。这是最自然的做法。 运行一个命令就是代码;您在函数中定义代码。您可以将函数的参数统称为
"$@"
.myScript () { /usr/bin/java -version "$@" } … myScript extra_argument_1 extra_argument_2
将可执行命令名称及其参数存储在一个数组中。
myScript=(/usr/bin/java -version) … "${myScript[@]}" extra_argument_1 extra_argument_2
存储一个shell命令,即要被shell解析的东西。要计算字符串中的 shell 代码,请使用
eval
。一定要像任何其他变量扩展一样引用参数,以避免过早的通配符扩展。这种方法更复杂,因为它需要仔细引用。它仅在必须将命令存储在字符串中时才真正有用,例如因为它作为脚本的参数出现。请注意,您不能以这种方式明智地传递额外的参数。myScript='/usr/bin/java -version' … eval "$myScript"
此外,由于您使用的是 ksh 而不是普通的 sh,因此您不需要使用 IFS
来拆分输入行。使用 read -A
直接分割成一个数组。
#!/usr/bin/ksh
CSV_FILE=
myScript=(/usr/bin/java -version)
while IFS='|' read -r -A columns
do
"${myScript[@]}"
for column in "${columns[@]}"
do
"${myScript[@]}"
done
done <"$CSV_FILE"
最简单的解决方案是避免更改 IFS
并像这样使用 read -d <delimiter>
进行拆分:
#!/usr/bin/ksh
CSV_FILE=
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