如何提取一个 csv 文件的一列

如果我有一个 csv 文件,是否有一种快速 bash 方法只打印出任何一列的内容?可以安全地假设每一行具有相同数量的列,但是每一列的内容具有不同的长度。

225880 次浏览

您可以使用 awk。将“ $2”更改为所需的第 n 列。

awk -F "\"*,\"*" '{print $2}' textfile.csv

是的,cat mycsv.csv | cut -d ',' -f3将印刷第三栏。

其他答案工作得很好,但是因为您要求使用 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-现在我们处于“ do”阶段,我们说回显数组“ csv _ line”的第0个元素。此操作在文件的每一行上重复。< file部分只是告诉 while 循环从哪里读取。注意: 记住,在 bash 中,数组是0索引的,所以第一列是第0个元素。

这就是它,从壳中的 CSV 中拉出一根柱子。其他解决方案可能更加实用,但是这个解决方案是纯 bash。

降落在这里寻找从一个制表符分离的文件提取。认为我会添加。

cat textfile.tsv | cut -f2 -s

其中 -f2提取2、非零索引列或第二列。

您可以使用 GNU Awk,请参阅 这篇文章的用户指南。 作为本文(2015年6月)中提供的解决方案的一个改进,下面的 gawk 命令允许双引号字段中包含双引号; 双引号由两个连续的双引号(“”)标记。此外,这允许使用空字段 但即使这样也不能处理多行字段。下面的示例打印 textfile.csv 的第3列(通过 c=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 作为换行符,请参阅 维基百科

如果输入可能包含多行字段,则可以使用以下脚本。注意在输出中使用特殊字符串来分隔记录(因为默认的分隔符换行符可能出现在记录中)。下面的示例再次打印 textfile.csv 的第3列(通过 c=3) :

#!/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可用于处理任意大型文件。

使用这个代码已经有一段时间了,它不是“快速”的,除非你把“从堆栈溢出中剪切和粘贴”也计算在内。

它在循环中使用 ${ # # }和 ${%% }运算符而不是 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

这些问题的许多答案都很棒,有些甚至已经研究了角落的案例。 我想添加一个简单的答案,可以在日常生活中使用... 你大部分进入那些角落情况(如转义逗号或逗号在引号等,)。

FS (字段分隔符)是其值默认为 所以在默认情况下,awk 对于任何行都会在空格处分裂。

因此,使用 BEGIN (在接受输入之前执行) ,我们可以将这个字段设置为任何我们想要的..。

awk 'BEGIN {FS = ","}; {print $3}'

上面的代码将在 csv 文件中打印第3列。

首先,我们将创建一个基本的 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

我能够做到这一点的最简单的方法就是使用 Csvtool。我还有使用 csvtool 的其他用例,如果引号或分隔符出现在列数据本身中,它可以适当地处理它们。

csvtool format '%(2)\n' input.csv

用列号替换2将有效地提取您正在查找的列数据。

没有一个完整的 CSV 解析器是无法完成的。

我需要正确的 CSV 解析,而不是 cut/awk和祈祷。我正在一台没有 csvtool的 Mac 上尝试这个,但是 Mac 上有 Ruby,所以你可以这样做:

echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby
csvtool col 2 file.csv

你感兴趣的栏目在哪里

你也可以

csvtool col 1,2 file.csv

做多个栏目

我想知道为什么到目前为止没有一个答案提到 CSVkit。

Csvkit 是一套用于转换和工作的命令行工具 和 CSV 一起

Csvkit 文档

我使用它专门为 csv 数据管理,到目前为止,我还没有发现一个问题,我不能解决使用 cvskit。

要从 cvs 文件中提取一个或多个列,可以使用工具箱中的 csvcut实用程序。要提取第二列,请使用以下命令:

csvcut -c 2 filename_in.csv > filename_out.csv

Csvcut 参考页面

如果 csv 中的字符串使用引号,则添加带有 q选项的引号字符:

csvcut -q '"' -c 2 filename_in.csv > filename_out.csv

使用 pip install csvkitsudo apt install csvkit安装。

下面是一个包含两列的 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 ...]

也可以使用 while 循环

IFS=,
while read name val; do
echo "............................"


echo Name: "$name"
done<itemlst.csv

我认为最简单的方法是使用 CSVKit:

获取第2列: csvcut -c 2 file.csv

但是,也有 Csvtool,可能还有其他一些 csv bash 工具:

sudo apt-get install csvtool(用于基于 Debian 的系统)

这将返回第一行中包含“ ID”的列。 csvtool namedcol ID csv_file.csv

这将返回第四行: csvtool col 4 csv_file.csv

如果要删除标题行:

csvtool col 4 csv_file.csv | sed '1d'

使用 awk 的简单解决方案。将需要打印的列数替换为“ colNum”。

cat fileName.csv | awk -F ";" '{ print $colNum }'

如果您知道您的数据将不会被引用,那么任何在 ,上分离的解决方案都将很好地工作(我倾向于使用 cut -d, -f1 | sed 1d) ,任何 CSV 操作工具也是如此。

如果您想生成另一个 CSV 文件,那么适合使用 xsvcsvkitcsvtool或其他 CSV 操作工具。

如果你想要一个 CSV 文件的一列的 把里面的东西提取出来没有引用它们,以便它们可以被后续的命令处理,这个 Python 1-liner 为带头的 CSV 文件做了这个技巧:

python -c 'import csv,sys'$'\n''for row in csv.DictReader(sys.stdin): print(row["message"])'

print函数内部的 "message"选择列。

如果 CSV 文件没有头:

python -c 'import csv,sys'$'\n''for row in csv.reader(sys.stdin): print(row[1])'

Python 的 CSV 库 支持所有种类的 CSV 方言,所以如果您的 CSV 文件使用不同的约定,那么可以对代码进行相对较小的更改来支持它们。