如果我有一个 csv 文件,是否有一种快速的 bash 方法可以仅打印出任何单个列的内容?可以安全地假设每行具有相同数量的列,但每列的内容将具有不同的长度。
17 回答
您可以为此使用 awk。将 '$2' 更改为您想要的第 n 列。
awk -F "\"*,\"*" '{print $2}' textfile.csv
是的。cat mycsv.csv | cut -d ',' -f3
将打印第三列。
我能够完成这项工作的最简单方法就是使用csvtool。我还有其他用例来使用 csvtool,如果引号或分隔符出现在列数据本身中,它可以适当地处理它们。
csvtool format '%(2)\n' input.csv
用列号替换 2 将有效地提取您要查找的列数据。
降落在这里,希望从制表符分隔的文件中提取。以为我会补充。
cat textfile.tsv | cut -f2 -s
Where-f2
提取第 2 个非零索引列或第二列。
这是一个包含 2 列的 csv 文件示例
myTooth.csv
Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom
要获取第一列,请使用:
cut -d, -f1 myTooth.csv
f 代表字段,d 代表分隔符
运行上述命令将产生以下输出。
输出
Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28
仅获取第二列:
cut -d, -f2 myTooth.csv
这是输出 输出
Tooth
wisdom
canine
canine
wisdom
incisor
另一个用例:
您的 csv 输入文件包含 10 列,并且您需要第 2 到第 5 列和第 8 列,使用逗号作为分隔符”。
cut 使用 -f(意思是“字段”)指定列,使用 -d(意思是“分隔符”)指定分隔符。您需要指定后者,因为某些文件可能使用空格、制表符或冒号来分隔列。
cut -f 2-5,8 -d , myvalues.csv
cut 是一个命令实用程序,这里有更多示例:
SYNOPSIS
cut -b list [-n] [file ...]
cut -c list [file ...]
cut -f list [-d delim] [-s] [file ...]
首先,我们将创建一个基本的 CSV
[dumb@one pts]$ cat > file
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
然后我们得到第一列
[dumb@one pts]$ awk -F , '{print $1}' file
a
1
a
1
这个问题的许多答案都很好,有些甚至研究了极端案例。我想添加一个可以日常使用的简单答案……您主要会遇到那些极端情况(例如转义逗号或引号中的逗号等)。
FS(字段分隔符)是其值默认为空格的变量。所以 awk 默认在空间分割任意行。
因此,使用 BEGIN(在输入之前执行)我们可以将此字段设置为我们想要的任何内容...
awk 'BEGIN {FS = ","}; {print $3}'
上面的代码将打印 csv 文件中的第三列。
其他答案效果很好,但由于您要求仅使用 bash shell 的解决方案,您可以这样做:
AirBoxOmega:~ d$ cat > file #First we'll create a basic CSV
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
然后你可以像这样拉出列(本例中的第一列):
AirBoxOmega:~ d$ while IFS=, read -a csv_line;do echo "${csv_line[0]}";done < file
a
1
a
1
a
1
a
1
a
1
a
1
所以这里发生了几件事:
while IFS=,
- 这就是说使用逗号作为 IFS(内部字段分隔符),这是 shell 用来知道分隔字段(文本块)的内容。所以说 IFS=, 就像说 "a,b" 与 "a b" 相同,如果 IFS=" " (默认情况下是这样)。read -a csv_line;
- 这就是说读取每一行,一次一个,并创建一个数组,其中每个元素都称为“csv_line”,并将其发送到我们的 while 循环的“do”部分do echo "${csv_line[0]}";done < file
- 现在我们处于“做”阶段,我们说的是回显数组“csv_line”的第 0 个元素。此操作在文件的每一行上重复。这< file
部分只是告诉 while 循环从哪里读取。注意:请记住,在 bash 中,数组的索引为 0,因此第一列是第 0 个元素。
所以你有了它,从 shell 中的 CSV 中拉出一列。其他解决方案可能更实用,但这个是纯 bash。
您可以使用 GNU Awk,请参阅用户指南的这篇文章。作为对文章(2015 年 6 月)中提出的解决方案的改进,以下 gawk 命令允许在双引号字段中使用双引号;双引号由两个连续的双引号 ("") 标记。此外,这允许空字段,但即使这样也不能处理多行字段。以下示例打印c=3
textfile.csv 的第 3 列(通过 ):
#!/bin/bash
gawk -- '
BEGIN{
FPAT="([^,\"]*)|(\"((\"\")*[^\"]*)*\")"
}
{
if (substr($c, 1, 1) == "\"") {
$c = substr($c, 2, length($c) - 2) # Get the text within the two quotes
gsub("\"\"", "\"", $c) # Normalize double quotes
}
print $c
}
' c=3 < <(dos2unix <textfile.csv)
请注意使用dos2unix
将可能的 DOS 样式换行符(CRLF 即“\r\n”)和 UTF-16 编码(带字节顺序标记)分别转换为“\n”和 UTF-8(不带字节顺序标记)。标准 CSV 文件使用 CRLF 作为换行符,请参阅Wikipedia。
如果输入可能包含多行字段,您可以使用以下脚本。请注意使用特殊字符串来分隔输出中的记录(因为默认分隔符换行符可能出现在记录中)。同样,以下示例打印c=3
textfile.csv 的第三列(通过):
#!/bin/bash
gawk -- '
BEGIN{
RS="\0" # Read the whole input file as one record;
# assume there is no null character in input.
FS="" # Suppose this setting eases internal splitting work.
ORS="\n####\n" # Use a special output separator to show borders of a record.
}
{
nof=patsplit($0, a, /([^,"\n]*)|("(("")*[^"]*)*")/, seps)
field=0;
for (i=1; i<=nof; i++){
field++
if (field==c) {
if (substr(a[i], 1, 1) == "\"") {
a[i] = substr(a[i], 2, length(a[i]) - 2) # Get the text within
# the two quotes.
gsub(/""/, "\"", a[i]) # Normalize double quotes.
}
print a[i]
}
if (seps[i]!=",") field=0
}
}
' c=3 < <(dos2unix <textfile.csv)
还有另一种解决问题的方法。csvquote可以输出修改后的 CSV 文件的内容,以便转换字段中的特殊字符,以便可以使用通常的 Unix 文本处理工具来选择特定列。例如以下代码输出第三列:
csvquote textfile.csv | cut -d ',' -f 3 | csvquote -u
csvquote
可用于处理任意大文件。
我需要正确的 CSV 解析,而不是cut
/awk
和祈祷。我在没有 的 mac 上尝试这个csvtool
,但是 mac 确实带有 ruby,所以你可以这样做:
echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby
我想知道为什么到目前为止没有一个答案提到 csvkit。
csvkit 是一套用于转换和使用 CSV 的命令行工具
我专门将它用于 csv 数据管理,到目前为止,我还没有发现使用 cvskit 无法解决的问题。
要从 cvs 文件中提取一个或多个列,您可以使用csvcut
工具箱中的实用程序。要提取第二列,请使用以下命令:
csvcut -c 2 filename_in.csv > filename_out.csv
如果 csv 中的字符串被引用,请在选项中添加引号字符q
:
csvcut -q '"' -c 2 filename_in.csv > filename_out.csv
使用pip install csvkit
或安装sudo apt install csvkit
。
csvtool col 2 file.csv
其中 2 是您感兴趣的列
你也可以
csvtool col 1,2 file.csv
做多列
使用 awk 的简单解决方案。而不是“colNum”放你需要打印的列数。
cat fileName.csv | awk -F ";" '{ print $colNum }'
如果没有完整的 CSV 解析器,您将无法做到这一点。
使用此代码有一段时间了,除非您计算“从 stackoverflow 剪切和粘贴”,否则它并不“快速”。
它在循环中使用 ${##} 和 ${%%} 运算符而不是 IFS。它调用“err”和“die”,并且仅支持逗号、破折号和管道作为 SEP 字符(这就是我所需要的)。
err() { echo "${0##*/}: Error:" "$@" >&2; }
die() { err "$@"; exit 1; }
# Return Nth field in a csv string, fields numbered starting with 1
csv_fldN() { fldN , "$1" "$2"; }
# Return Nth field in string of fields separated
# by SEP, fields numbered starting with 1
fldN() {
local me="fldN: "
local sep="$1"
local fldnum="$2"
local vals="$3"
case "$sep" in
-|,|\|) ;;
*) die "$me: arg1 sep: unsupported separator '$sep'" ;;
esac
case "$fldnum" in
[0-9]*) [ "$fldnum" -gt 0 ] || { err "$me: arg2 fldnum=$fldnum must be number greater or equal to 0."; return 1; } ;;
*) { err "$me: arg2 fldnum=$fldnum must be number"; return 1;} ;;
esac
[ -z "$vals" ] && err "$me: missing arg2 vals: list of '$sep' separated values" && return 1
fldnum=$(($fldnum - 1))
while [ $fldnum -gt 0 ] ; do
vals="${vals#*$sep}"
fldnum=$(($fldnum - 1))
done
echo ${vals%%$sep*}
}
例子:
$ CSVLINE="example,fields with whitespace,field3"
$ $ for fno in $(seq 3); do echo field$fno: $(csv_fldN $fno "$CSVLINE"); done
field1: example
field2: fields with whitespace
field3: field3
You can also use while loop
IFS=,
while read name val; do
echo "............................"
echo Name: "$name"
done<itemlst.csv