如何使用sed
或对 CSV 文件执行以下操作awk
?
- 删除一列
- 复制列
- 移动一列
我有一个超过 200 行的大表,我对sed
.
答案1
这取决于您的 CSV 文件是否仅使用逗号作为分隔符,或者您是否有这样的疯狂:
字段一、“字段、二”、字段三
假设您使用的是简单的 CSV 文件:
删除列
您可以通过多种方式摆脱单个列;我以第 2 列为例。最简单的方法可能是使用cut
,它可以让您指定分隔符-d
以及要打印的字段-f
;这告诉它以逗号分割并输出字段 1 和字段 3 到末尾:
$ cut -d, -f1,3- /path/to/your/file
如果确实需要使用sed
,可以写一个正则表达式,匹配第一个n-1
字段、n
第个字段和其余字段,并跳过输出第n
th 个字段(这里n
是 2,所以第一组是匹配1
时间:)\{1\}
:
$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file
有很多方法可以做到这一点awk
,但没有一种方法特别优雅。您可以使用for
循环,但处理尾随逗号很痛苦;忽略它会是这样的:
$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file
我发现输出字段 1 然后用于substr
完成字段 2 之后的所有内容更容易:
$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file
不过,这对于进一步的列来说很烦人
复制列
这本质上sed
与之前的表达式相同,但您还捕获目标列并在替换中多次包含该组:
$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file
在awk
for 循环方式中,它会类似于(再次忽略尾随逗号):
$ awk -F, '{
for(i=1; i<=NF; i++) {
if(i == 2) printf "%s,", $i;
printf "%s,", $i
}
print NL
}' /path/to/your/file
道路substr
:
$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file
(tcdyl 提出了一个更好的方法他的回答)
移动列
我认为该sed
解决方案自然可以从其他解决方案中得出,但它开始变得非常长
答案2
awk
是你最好的选择。awk
按数字打印字段,所以...
awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file
要删除列,而不打印它:
awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file
要更改顺序:
awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file
重定向到输出文件。
awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file
awk
也可以格式化输出。
答案3
除了如何剪切和重新排列字段(在其他答案中介绍)之外,还存在奇怪的 CSV 字段问题。
如果您的数据属于这个“古怪”类别,那么前和邮政过滤可以解决它。下面显示的过滤器要求字符\x01
, \x02
, \x03
,\x04
不得出现在数据中的任何位置。
以下是围绕简单awk
字段转储的过滤器。
笔记: 五场具有无效/不完整的“引用字段”布局,但它在行末尾是良性的(取决于 CSV 解析器)。但是,这当然会导致有问题的意外结果如果将其从当前的状态换掉行尾位置。
更新;用户121196指出了逗号位于尾随引号之前的错误。这是修复方法。
数据
cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF
代码
sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g'
输出:
field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five
"15111 N. Hayden Rd., Ste 160,"
""
这里是预过滤器,通过评论进行扩展。
这后置过滤器只是一个逆转\x01
。\x02
, \x03
,\x04
sed -r '
s/^/,/ # add a leading comma delimiter
s/\\"/\x01/g # obfuscate escaped quotation-mark (\")
s/,"([^"]*)"/,\x02\1\x03/g # obfuscate quotation-marks
s/,"/,\x02/ # when no trailing quote on last field
:MC # obfuscate commas embedded in quotes
s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
tMC
s/^,// # remove spurious leading delimiter
'
答案4
要处理 CSV 数据,通常最好使用支持 CSV 的工具,例如磨坊主或由csvkit。和sed
都是awk
通用文本处理实用程序,它们对 CSV 及其引用规则等一无所知。
测试数据:
id,name,date of birth
1,"Alfonso, the first",1980-01-01
2,"Betty, the second",1980-01-02
3,"Conny, the third",1982-02-21
name
要使用 csvkit 工具删除该字段:
$ csvcut -C name file
id,date of birth
1,1980-01-01
2,1980-01-02
3,1982-02-21
要使用 csvkit 复制字段name
:
$ csvcut -c id,name,name,"date of birth" file
id,name,name,date of birth
1,"Alfonso, the first","Alfonso, the first",1980-01-01
2,"Betty, the second","Betty, the second",1980-01-02
3,"Conny, the third","Conny, the third",1982-02-21
要首先使用 csvkit 移动date of birth
字段:
$ csvcut -c "date of birth",id,name file
date of birth,id,name
1980-01-01,1,"Alfonso, the first"
1980-01-02,2,"Betty, the second"
1982-02-21,3,"Conny, the third"
要使用 Miller 删除该name
字段:
$ mlr --csv cut -x -f name file
id,date of birth
1,1980-01-01
2,1980-01-02
3,1982-02-21
要使用 Miller 复制name
字段(name2
最后创建为新字段):
$ mlr --csv put '$name2 = $name' file
id,name,date of birth,name2
1,"Alfonso, the first",1980-01-01,"Alfonso, the first"
2,"Betty, the second",1980-01-02,"Betty, the second"
3,"Conny, the third",1982-02-21,"Conny, the third"
要使用 Miller 将 移动date of birth
到记录的开头:
$ mlr --csv reorder -f "date of birth" file
date of birth,id,name
1980-01-01,1,"Alfonso, the first"
1980-01-02,2,"Betty, the second"
1982-02-21,3,"Conny, the third"