148

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

4

17 回答 17

175

您可以为此使用 awk。将 '$2' 更改为您想要的第 n 列。

awk -F "\"*,\"*" '{print $2}' textfile.csv
于 2013-10-26T02:34:38.597 回答
127

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

于 2013-10-26T02:37:17.773 回答
78

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

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

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

于 2016-10-25T18:36:58.950 回答
19

降落在这里,希望从制表符分隔的文件中提取。以为我会补充。

cat textfile.tsv | cut -f2 -s

Where-f2提取第 2 个非零索引列或第二列。

于 2014-04-18T20:28:33.170 回答
12

这是一个包含 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 ...]
于 2019-02-17T17:17:54.503 回答
10

我认为最简单的是使用csvkit

获取第二列: 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'

于 2019-08-15T01:11:46.243 回答
8

首先,我们将创建一个基本的 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
于 2016-03-14T21:53:43.460 回答
7

这个问题的许多答案都很好,有些甚至研究了极端案例。我想添加一个可以日常使用的简单答案……您主要会遇到那些极端情况(例如转义逗号或引号中的逗号等)。

FS(字段分隔符)是其值默认为空格的变量。所以 awk 默认在空间分割任意行。

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

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

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

于 2015-12-16T02:59:53.733 回答
6

其他答案效果很好,但由于您要求仅使用 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。

于 2013-10-26T05:29:27.580 回答
5

您可以使用 GNU Awk,请参阅用户指南的这篇文章。作为对文章(2015 年 6 月)中提出的解决方案的改进,以下 gawk 命令允许在双引号字段中使用双引号;双引号由两个连续的双引号 ("") 标记。此外,这允许空字段,但即使这样也不能处理多行字段。以下示例打印c=3textfile.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=3textfile.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可用于处理任意大文件。

于 2015-06-06T17:33:45.207 回答
5

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

echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby
于 2018-01-18T20:58:30.470 回答
4

我想知道为什么到目前为止没有一个答案提到 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 csvkit或安装sudo apt install csvkit

于 2018-12-13T15:38:10.027 回答
3
csvtool col 2 file.csv 

其中 2 是您感兴趣的列

你也可以

csvtool col 1,2 file.csv 

做多列

于 2018-09-04T09:08:46.187 回答
2

使用 awk 的简单解决方案。而不是“colNum”放你需要打印的列数。

cat fileName.csv | awk -F ";" '{ print $colNum }'
于 2021-05-02T14:34:32.397 回答
1

如果没有完整的 CSV 解析器,您将无法做到这一点。

于 2016-12-26T01:22:23.697 回答
0

使用此代码有一段时间了,除非您计算“从 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
于 2015-12-08T15:19:32.310 回答
0

You can also use while loop

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

        echo Name: "$name"
done<itemlst.csv
于 2019-03-13T06:08:46.413 回答