假设我有以下 csv 文件:
id,message,time
123,"Sorry, This message
has commas and newlines",2016-03-28T20:26:39
456,"It makes the problem non-trivial",2016-03-28T20:26:41
我想编写一个仅返回时间列的 bash 命令。即
time
2016-03-28T20:26:39
2016-03-28T20:26:41
最直接的方法是什么?您可以假设标准 UNIX 实用程序的可用性,例如 awk、gawk、cut、grep 等。
注意转义的“”的存在,以及使用
进行简单尝试的换行符cut -d , -f 3 file.csv
徒劳。
正如chepner所说,我们鼓励您使用能够解析csv的编程语言。
这是一个Python示例:
import csv
with open('a.csv', 'rb') as csvfile:
reader = csv.reader(csvfile, quotechar='"')
for row in reader:
print(row[-1]) # row[-1] gives the last column
正如这里所说
gawk -v RS='"' 'NR % 2 == 0 { gsub(/\n/, "") } { printf("%s%s", $0, RT) }' file.csv \
| awk -F, '{print $NF}'
要专门处理双引号字符串中的换行符,并保留它们之外的换行符,请使用
GNU awk
(对于 RT
):
gawk -v RS='"' 'NR % 2 == 0 { gsub(/\n/, "") } { printf("%s%s", $0, RT) }' file
这可以通过沿
"
字符拆分文件并删除每个其他块中的换行符来实现。
输出
time
2016-03-28T20:26:39
2016-03-28T20:26:41
然后使用awk分割列并显示最后一列
csvcut
来自 csvkit
示例
csvkit 在以下位置提到:https://stackoverflow.com/a/36288388/895245 但这里是示例。
安装:
pip install csvkit
CSV 输入文件示例:
主.csv
a,"b
c",d
e,f
获取第一列:
csvcut -c 1 main.csv
输出:
a
e
或者获取第二列:
csvcut -c 1 main.csv
输出以下有效的单列 CSV:
"b
c"
f
或者交换两列:
csvcut -c 2,1 main.csv
输出另一个有效的 CSV 文件:
"b
c",a
f,e
在 Ubuntu 23.04 上测试,csvkit==1.1.1。
csv工具
这又是一件好事。它是编译后的可执行文件而不是 Python 脚本,因此对于大型数据集来说速度要快得多。如果您想打印没有转义的单列,那么
format
操作很有用,只要没有多行条目,这就会很有用,而 csvkit
似乎不支持。
安装:
sudo apt install csvtool
使用示例:
printf 'a,"b,c",d\ne,"f""g",h\n' | csvtool format '%(2)\n' -
输出:
b,c
f"g
另请参阅:如何提取 csv 文件的一列
在 Ubuntu 23.10、csvtool 2.4-3 上测试。
使用 FS 的另一种
awk
替代方案
$ awk -F'"' '!(NF%2){getline remainder;$0=$0 OFS remainder}
NR>1{sub(/,/,"",$NF); print $NF}' file
2016-03-28T20:26:39
2016-03-28T20:26:41
在尝试处理 lspci -m 输出时,我遇到了类似的情况,但嵌入的换行符需要首先转义(尽管 IFS=, 应该在这里工作,因为它滥用了 bash 的引用评估)。 这是一个例子
f:13.3 "System peripheral" "Intel Corporation" "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel Target Address Decoder" -r01 "Super Micro Computer Inc" "Device 0838"
我能找到将其引入 bash 的唯一合理方法是:
# echo 'f:13.3 "System peripheral" "Intel Corporation" "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel Target Address Decoder" -r01 "Super Micro Computer Inc" "Device 0838"' | { eval array=($(cat)); declare -p array; }
declare -a array='([0]="f:13.3" [1]="System peripheral" [2]="Intel Corporation" [3]="Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel Target Address Decoder" [4]="-r01" [5]="Super Micro Computer Inc" [6]="Device 0838")'
#
不是完整的答案,但可能有帮助!
原版 bash 脚本
将此代码保存为parse_csv.sh,并赋予其执行权限(chmod +x parse_csv.sh)
#!/bin/bash
# vim: ts=4 sw=4 hidden nowrap
# @copyright Copyright © 2021 Carlos Barcellos <carlosbar at gmail.com>
# @license https://www.gnu.org/licenses/lgpl-3.0.en.html
if [ "$1" = "-h" -o "$1" = "--help" -o "$1" = "-v" ]; then
echo "parse csv 0.1"
echo ""
echo "parse_csv.sh [csv file] [delimiter]"
echo " csv file csv file to parse; default stdin"
echo " delimiter delimiter to use. default is comma"
exit 0
fi
delim=,
if [ $# -ge 1 ]; then
[ -n "$1" ] && file="$1"
[ -n "$2" -a "$2" != "\"" ] && delim="$2"
fi
processLine() {
if [[ ! "$1" =~ \" ]]; then
(
IFSS="$delim" fields=($1)
echo "${fields[@]}"
)
return 0
fi
under_scape=0
fields=()
acc=
for (( x=0; x < ${#1}; x++ )); do
if [ "${1:x:1}" = "${delim:0:1}" -o $((x+1)) -ge ${#1} ] && [ $under_scape -ne 1 ]; then
[ "${1:x:1}" != "${delim:0:1}" ] && acc="${acc}${1:x:1}"
fields+=($acc)
acc=
elif [ "${1:x:1}" = "\"" ]; then
if [ $under_scape -eq 1 ] && [ "${1:x+1:1}" = "\"" ]; then
acc="${acc}${1:x:1}"
else
under_scape=$((!under_scape))
fi
[ $((x+1)) -ge ${#1} ] && fields+=($acc)
else
acc="${acc}${1:x:1}"
fi
done
echo "${fields[@]}"
return 0
}
while read -r line; do
processLine "$line"
done < ${file:-/dev/stdin}
然后使用:parse_csv.sh“csv文件”。要仅打印最后一列,您可以将 echo "${fields[@]}" 更改为 echo "${fields[-1]}"
Perl 来救援!使用 Text::CSV_XS 模块来处理 CSV。
perl -MText::CSV_XS=csv -we 'csv(in => $ARGV[0],
on_in => sub { $_[1] = [ $_[1][-1] ] })
' -- file.csv
csv
子例程处理 csvin
指定输入文件,$ARGV[0]
包含第一个命令行参数,即这里的file.csv
on_in
指定要运行的代码。它将当前行作为第二个参数,即 $_[1]
。我们只是将整行设置为最后一列的内容。我觉得你想多了。
$: echo time; grep -Eo '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$' file
time
2016-03-28T20:26:39
2016-03-28T20:26:41
如果您想检查该逗号只是为了确定,
$: echo time; sed -En '/,[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/{ s/.*,//; p; }' file
time
2016-03-28T20:26:39
2016-03-28T20:26:41
csvquote 正是为此类事情而设计的。它对文件进行消毒(可逆),并允许 awk 依赖逗号作为字段分隔符,将换行符作为记录分隔符。
awk -F, '!/This/{print $NF}' file
time
2016-03-28T20:26:39
2016-03-28T20:26:41