Bash/Shell |如何在阅读中优先考虑来自 IFS 的报价
Bash/Shell | How to prioritize quote from IFS in read
我正在使用手填文件,但在解析它时遇到问题。
我的文件输入文件无法更改,我的代码语言无法从 bash 脚本更改。
我做了一个简单的例子,方便大家看^^
var="hey","i'm","happy, like","you"
IFS="," read -r one two tree for five <<<"$var"
echo $one:$two:$tree:$for:$five
现在我想你已经看到这里的问题了。我想得到
hey:i'm:happy, like:you:
但我明白了
hey:i'm:happy: like:you
我需要一种方法来告诉 read
“ ”比 IFS 更重要。我已经阅读了 eval
命令,但我不能冒这个风险。
最后是一个目录文件,麻烦的字段是描述字段,所以它基本上可以包含任何内容。
原始文件看起来像那样
"type","cn","uid","gid","gecos","description","timestamp","disabled"
"type","cn","uid","gid","gecos","description","timestamp","disabled"
"type","cn","uid","gid","gecos","description","timestamp","disabled"
编辑#1
我会举一个更好的例子;我在上面使用的那个太简单了,@StefanHegny 发现它会导致另一个错误。
while read -r ldapLine
do
IFS=',' read -r objectClass dumy1 uidNumber gidNumber username description modifyTimestamp nsAccountLock gecos homeDirectory loginShell createTimestamp dumy2 <<<"$ldapLine"
isANetuser=0
while IFS=":" read -r -a class
do
for i in "${class[@]}"
do
if [ "$i" == "account" ]
then
isANetuser=1
break
fi
done
done <<< $objectClass
if [ $isANetuser == 0 ]
then
continue
fi
#MORE STUFF APPEND#
done < file.csv
所以这是代码的一小部分,但它应该解释我所做的事情。 file.csv
有很多这样的行:
"top:shadowAccount:account:posixAccount","Jdupon","12345","6789","Jdupon","Jean Mark, Dupon","20140511083750Z","","Jean Mark, Dupon","/home/user/Jdupon","/bin/ksh","20120512083750Z","",""
我的建议,如之前的一些回答(见下文),是将分隔符切换为 |
(并改用 IFS="|"
):
sed -r 's/,([^,"]*|"[^"]*")/|/g'
然而,这需要 sed
扩展正则表达式 (-r
)。
Should I use AWK or SED to remove commas between quotation marks from a CSV file? (BASH)
如果您将使用的各种bash
版本都比v3.0更新,当引入正则表达式和BASH_REMATCH
时,您可以使用类似以下功能的东西:[注1]
each_field () {
local v=,;
while [[ $v =~ ^,(([^\",]*)|\"[^\"]*\") ]]; do
printf "%s\n" "${BASH_REMATCH[2]:-${BASH_REMATCH[1]:1:-1}}";
v=${v:${#BASH_REMATCH[0]}};
done
}
它的参数是一行(记得引用它!)并且它在单独的行上打印每个逗号分隔的字段。如所写,它假定没有字段包含换行符;这在 CSV 中是合法的,但它使将文件分成几行变得更加复杂。如果您确实需要处理这种情况,您可以将 printf 语句中的 \n
更改为 [=17=]
,然后使用类似 xargs -0
的内容来处理输出。 (或者您可以插入您需要对字段执行的任何处理来代替 printf
语句。)
在不修改未引用字段的情况下取消引用字段会带来一些麻烦。但是,它将在嵌入双引号的字段上失败。如果需要,这是可以修复的。 [注2]
这是一个示例,以防不明显:
while IFS= read -r line; do
each_field "$line"
printf "%s\n" "-----"
done <<EOF
type,cn,uid,gid,gecos,"description",timestamp,disabled
"top:shadowAccount:account:posixAccount","Jdupon","12345","6789","Jdupon","Jean Mark, Dupon","20140511083750Z","","Jean Mark, Dupon","/home/user/Jdupon","/bin/ksh","20120512083750Z","",""
EOF
输出:
type
cn
uid
gid
gecos
description
timestamp
disabled
-----
top:shadowAccount:account:posixAccount
Jdupon
12345
6789
Jdupon
Jean Mark, Dupon
20140511083750Z
Jean Mark, Dupon
/home/user/Jdupon
/bin/ksh
20120512083750Z
-----
备注:
我不是说你应该使用这个功能。您应该使用 CSV 解析器,或包含良好 CSV 解析库的语言,例如 python。但我相信这个 bash 函数可以在某种常见 CSV 方言的格式正确的 CSV 文件上工作,尽管速度很慢。
这是一个处理引号字段内双引号的版本,这是内部引号的经典 CSV 语法:
each_field () {
local v=,;
while [[ $v =~ ^,(([^\",]*)|\"(([^\"]|\"\")*)\") ]]; do
echo "${BASH_REMATCH[2]:-${BASH_REMATCH[3]//\"\"/\"}}";
v=${v:${#BASH_REMATCH[0]}};
done
}
我正在使用手填文件,但在解析它时遇到问题。 我的文件输入文件无法更改,我的代码语言无法从 bash 脚本更改。
我做了一个简单的例子,方便大家看^^
var="hey","i'm","happy, like","you"
IFS="," read -r one two tree for five <<<"$var"
echo $one:$two:$tree:$for:$five
现在我想你已经看到这里的问题了。我想得到
hey:i'm:happy, like:you:
但我明白了
hey:i'm:happy: like:you
我需要一种方法来告诉 read
“ ”比 IFS 更重要。我已经阅读了 eval
命令,但我不能冒这个风险。
最后是一个目录文件,麻烦的字段是描述字段,所以它基本上可以包含任何内容。
原始文件看起来像那样
"type","cn","uid","gid","gecos","description","timestamp","disabled"
"type","cn","uid","gid","gecos","description","timestamp","disabled"
"type","cn","uid","gid","gecos","description","timestamp","disabled"
编辑#1
我会举一个更好的例子;我在上面使用的那个太简单了,@StefanHegny 发现它会导致另一个错误。
while read -r ldapLine
do
IFS=',' read -r objectClass dumy1 uidNumber gidNumber username description modifyTimestamp nsAccountLock gecos homeDirectory loginShell createTimestamp dumy2 <<<"$ldapLine"
isANetuser=0
while IFS=":" read -r -a class
do
for i in "${class[@]}"
do
if [ "$i" == "account" ]
then
isANetuser=1
break
fi
done
done <<< $objectClass
if [ $isANetuser == 0 ]
then
continue
fi
#MORE STUFF APPEND#
done < file.csv
所以这是代码的一小部分,但它应该解释我所做的事情。 file.csv
有很多这样的行:
"top:shadowAccount:account:posixAccount","Jdupon","12345","6789","Jdupon","Jean Mark, Dupon","20140511083750Z","","Jean Mark, Dupon","/home/user/Jdupon","/bin/ksh","20120512083750Z","",""
我的建议,如之前的一些回答(见下文),是将分隔符切换为 |
(并改用 IFS="|"
):
sed -r 's/,([^,"]*|"[^"]*")/|/g'
然而,这需要 sed
扩展正则表达式 (-r
)。
Should I use AWK or SED to remove commas between quotation marks from a CSV file? (BASH)
如果您将使用的各种bash
版本都比v3.0更新,当引入正则表达式和BASH_REMATCH
时,您可以使用类似以下功能的东西:[注1]
each_field () {
local v=,;
while [[ $v =~ ^,(([^\",]*)|\"[^\"]*\") ]]; do
printf "%s\n" "${BASH_REMATCH[2]:-${BASH_REMATCH[1]:1:-1}}";
v=${v:${#BASH_REMATCH[0]}};
done
}
它的参数是一行(记得引用它!)并且它在单独的行上打印每个逗号分隔的字段。如所写,它假定没有字段包含换行符;这在 CSV 中是合法的,但它使将文件分成几行变得更加复杂。如果您确实需要处理这种情况,您可以将 printf 语句中的 \n
更改为 [=17=]
,然后使用类似 xargs -0
的内容来处理输出。 (或者您可以插入您需要对字段执行的任何处理来代替 printf
语句。)
在不修改未引用字段的情况下取消引用字段会带来一些麻烦。但是,它将在嵌入双引号的字段上失败。如果需要,这是可以修复的。 [注2]
这是一个示例,以防不明显:
while IFS= read -r line; do
each_field "$line"
printf "%s\n" "-----"
done <<EOF
type,cn,uid,gid,gecos,"description",timestamp,disabled
"top:shadowAccount:account:posixAccount","Jdupon","12345","6789","Jdupon","Jean Mark, Dupon","20140511083750Z","","Jean Mark, Dupon","/home/user/Jdupon","/bin/ksh","20120512083750Z","",""
EOF
输出:
type
cn
uid
gid
gecos
description
timestamp
disabled
-----
top:shadowAccount:account:posixAccount
Jdupon
12345
6789
Jdupon
Jean Mark, Dupon
20140511083750Z
Jean Mark, Dupon
/home/user/Jdupon
/bin/ksh
20120512083750Z
-----
备注:
我不是说你应该使用这个功能。您应该使用 CSV 解析器,或包含良好 CSV 解析库的语言,例如 python。但我相信这个 bash 函数可以在某种常见 CSV 方言的格式正确的 CSV 文件上工作,尽管速度很慢。
这是一个处理引号字段内双引号的版本,这是内部引号的经典 CSV 语法:
each_field () { local v=,; while [[ $v =~ ^,(([^\",]*)|\"(([^\"]|\"\")*)\") ]]; do echo "${BASH_REMATCH[2]:-${BASH_REMATCH[3]//\"\"/\"}}"; v=${v:${#BASH_REMATCH[0]}}; done }