Awk/sed替换换行符

5

介绍:

我收到了一份 CSV 文件,其中字段分隔符是管道字符(例如 |)。这个文件有一个预定义的字段数量(假设为 N)。我可以通过读取 CSV 文件的头部来发现 N 的值,我们可以假定它是正确的。

问题:

一些字段错误地包含了换行符,这使得该行看起来比需要的更短(即,它具有 M 个字段,其中 M < N)。

我需要创建一个 sh 脚本(而不是 bash)来修复这些行。

尝试的解决方案:

我尝试创建以下脚本来尝试修复文件:

if [ $# -ne 1 ]
then
    echo "Usage: $0 <filename>"
    exit
fi

# get first line
first_line=$(head -n 1 $1)

# get number of fields
num_separators=$(echo "$first_line" | tr -d -c '|' | awk '{print length}')

cat $1  | awk -v numFields=$(( num_separators + 1 )) -F '|' '
{
    totRecords = NF/numFields
    # loop over lines
    for (record=0; record < totRecords; record++) {
        output = ""
        # loop over fields
        for (i=0; i<numFields; i++) {
            j = (numFields*record)+i+1 
            # replace newline with question mark
            sub("\n", "?", $j)
            output = output (i > 0 ? "|" : "") $j 
        }
        print output
    }
}
'

然而,换行符仍然存在。 我该如何解决这个问题?

CSV示例:

FIRST_NAME|LAST_NAME|NOTES
John|Smith|This is a field with a
newline
Foo|Bar|Baz

预期输出:

FIRST_NAME|LAST_NAME|NOTES
John|Smith|This is a field with a * newline
Foo|Bar|Baz

* I don't care about the replacement, it could be a space, a question mark, whatever except a newline or a pipe (which would create a new field)

你能否提供一个样例输入和期望输出? - anubhava
假设一个字段包含换行符,那么可以安全地假定该记录包含的字段数量比标题少,并且下一条记录也是如此吗? - JNevill
@EdMorton 我添加了预期输出,谢谢! - user2340612
1
关于样例输入,没有更多信息的情况下,我认为无法知道额外的换行符是在第一条记录的最后一个字段中,还是第二条记录的第一个字段中。当然,作为人类,我们可以看出你的意思是它应该在第一条记录的最后一个字段中,但程序怎么知道呢? - jas
1
@jas 很好的观察,我忘了提到。据我所知(我不是这个文件的制作人),第一个字段不应该由人输入(它还有其他输入字段,可能会不小心插入换行符),但它是一种标识,因此我们可以安全地假设第一个字段不包含任何奇怪的字符。在这种情况下,错误必定存在于最后一个字段中。 - user2340612
显示剩余6条评论
2个回答

7
$ cat tst.awk
BEGIN { FS=OFS="|" }
NR==1 { reqdNF = NF; printf "%s", $0; next }
{ printf "%s%s", (NF < reqdNF ? " " : ORS), $0 }
END { print "" }

$ awk -f tst.awk file.csv
FIRST_NAME|LAST_NAME|NOTES
John|Smith|This is a field with a newline
Foo|Bar|Baz

如果这不是你想要的,请编辑你的问题,提供更真实代表性的样本输入和相关输出。

1
这样会好很多 +1 - anubhava
1
非常好的答案,我甚至不需要“初始化”脚本。我以前不知道awk,但我必须承认它真的很强大。 - user2340612
2
没错,如果你需要进行任何文本操作并且可以使用CLI,我强烈推荐Arnold Robbins的《Effective Awk Programming, 4th Edition》这本书。 - Ed Morton
1
这真是太棒了。我使用awk进行简单的字段切割,但这太优雅了。刚刚购买了@EdMorton推荐的文本。 - Peter

1

基于最后一个字段可能包含一个换行符的假设。使用tacsed

tac file.csv | sed -n '/|/!{h;n;x;H;x;s/\n/ * /p;b};p' | tac 

输出:

FIRST_NAME|LAST_NAME|NOTES
John|Smith|This is a field with a * newline
Foo|Bar|Baz

它是如何工作的。倒着读文件,sed 没有前向引用更容易。如果一行没有 '|' 分隔符,则运行花括号 {}; 中的代码块 /|/!,否则只需打印该行 p。 代码块:
  1. h; 将无分隔符的行存储在 sed保持 缓冲区中。
  2. n; 获取另一行,由于我们是倒着读取的,因此这是应附加到的行 之一
  3. x; 交换保持缓冲区和模式缓冲区。
  4. H; 将模式缓冲区附加到保持缓冲区
  5. x; 交换新添加的行到模式缓冲区,现在有两行在一个缓冲区中。
  6. s/\n/ * /p; 用 " * " 替换中间的换行符,现在只有一条 更长 的线;并打印。
  7. b 重新开始,离开代码块。
tac 再次反转文件;完成。

嗨agc,感谢您的回答。您的代码基于的假设是额外的换行符在最后一个字段中,对吗? - user2340612
好的,谢谢。我只是想在使用它之前澄清这一点 :) 不过解释得很好! - user2340612

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接