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


-----

备注:

  1. 我不是说你应该使用这个功能。您应该使用 CSV 解析器,或包含良好 CSV 解析库的语言,例如 python。但我相信这个 bash 函数可以在某种常见 CSV 方言的格式正确的 CSV 文件上工作,尽管速度很慢。

  2. 这是一个处理引号字段内双引号的版本,这是内部引号的经典 CSV 语法:

    each_field () { 
        local v=,;
        while [[ $v =~ ^,(([^\",]*)|\"(([^\"]|\"\")*)\") ]]; do
            echo "${BASH_REMATCH[2]:-${BASH_REMATCH[3]//\"\"/\"}}";
            v=${v:${#BASH_REMATCH[0]}};
        done
    }