【问题标题】:How to extract one column of a csv file如何提取csv文件的一列
【发布时间】:2013-11-05 07:12:11
【问题描述】:

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

【问题讨论】:

标签: bash csv extract


【解决方案1】:

使用 awk 的简单解决方案。代替“colNum”,输入您需要打印的列数。

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

【讨论】:

  • 这比公认的答案好多少?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我认为最简单的方法是使用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'

    【讨论】:

      【解决方案4】:

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

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

      csvkit documentation

      我专门将它用于 csv 数据管理,到目前为止我还没有发现使用 cvskit 无法解决的问题。

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

      csvcut -c 2 filename_in.csv > filename_out.csv 
      

      csvcut reference page

      如果 csv 中的字符串被引用,请使用 q 选项添加引号字符:

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

      使用pip install csvkitsudo apt install csvkit 安装。

      【讨论】:

      • 也可以通过homebrew获得。
      【解决方案5】:

      你也可以使用while循环

      IFS=,
      while read name val; do
              echo "............................"
      
              echo Name: "$name"
      done<itemlst.csv
      

      【讨论】:

      • 此代码产生一个 Shellcheck 警告:SC2034。在寻找回避警告的方法时,搜索会将此问题作为第一个结果返回。
      【解决方案6】:

      这是一个包含 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 ...]
      

      【讨论】:

        【解决方案7】:
        csvtool col 2 file.csv 
        

        其中 2 是您感兴趣的列

        你也可以这样做

        csvtool col 1,2 file.csv 
        

        做多列

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

            您可以使用 GNU Awk,请参阅 this article of the user guide。 作为对文章(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 作为换行符,请参阅 Wikipedia

            如果输入可能包含多行字段,您可以使用以下脚本。注意在输出中使用特殊字符串分隔记录(因为默认分隔符换行符可能出现在记录中)。同样,以下示例打印 textfile.csv 的第三列(通过 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 可用于处理任意大文件。

            【讨论】:

              【解决方案10】:

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

              【讨论】:

              • 什么时候算作一个完整的 CSV 解析器? cut 算不算?
              【解决方案11】:

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

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

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

              【讨论】:

              • 这应该是公认的答案。该工具知道如何处理 CSV 文件,而不仅仅是将逗号视为字段分隔符。要提取第二列,“csvtool col 2 input.csv”
              • 请注意...如果您想将 csvtool 与标准输入一起使用(示例 csv 来自另一个命令),就像这样 cat input.csv | csvtool formath '%(2)\n' - 注意我知道 cat 在这里没用,但将其替换为通常会导出 csv 的任何命令。
              • 如果有多行字段,format '%(2)\n' 命令无法判断一个字段的结束位置。 (csvtool 1.4.2)
              • 较新版本的csvtool 似乎需要使用- 作为从标准输入读取的输入文件名。
              • 重申我在评论中的内容。 > 注意我知道 cat 在这里是无用的,但将它用于通常会导出 csv 的任何命令。 – 换句话说,我用它作为可能输出 csv 内容的其他命令的示例(如 drush)。不幸的是,2 年后我无法编辑我的评论,所以它永远打错了
              【解决方案12】:

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

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

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

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

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

              【讨论】:

              • 我已经尝试过了,它仍然认为引号字段中的逗号。
              【解决方案13】:

              使用这段代码有一段时间了,除非你算上“从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
              

              【讨论】:

                【解决方案14】:

                登陆这里是为了从制表符分隔的文件中提取内容。以为我会补充。

                cat textfile.tsv | cut -f2 -s
                

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

                【讨论】:

                • 简单,也很重要,并且比其他示例更容易适应。谢谢!
                • 吹毛求疵,但cat 是不必要的:&lt; textfile.tsv cut -f2 -s
                • 对多列使用-f1,2,3
                • 另外,你必须添加-d选项来确定分隔符,最后的命令是:cat file.csv |剪切 -f2 -s -d,
                【解决方案15】:

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

                这样就完成了,从 shell 中的 CSV 中拉出一列。其他解决方案可能更实用,但这个是纯 bash。

                【讨论】:

                  【解决方案16】:

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

                  【讨论】:

                  • 除非第二列包含逗号,在这种情况下,您将获得第二列的后半部分。以 ,"3,000", 为例。不过,就这个问题而言,我的回答并没有好多少。所以不要灰心。
                  • @synthesizerpatel 我同意更好地使用awk
                  • 我们不确定他的 CSV 文件是否包含双引号来区分不同的值。他最好提供一个输入文件,以便我们评估最合适的解决方案。
                  • 这是标准。如果没有,那将是一个格式错误的 csv。
                  【解决方案17】:

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

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

                  【讨论】:

                  • echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}' 将打印 2 而不是 2,3,4,5
                  • 如果您是在 Windows 中使用 GNU 工具的幸运儿,您可以执行与 @IgorMikushkin 相同的命令,如下所示:gawk -F"|" "{print $13}" files*.csv
                  • 我认为当有包含逗号的字符串时这会失败,即...,"string,string",...
                  • 我认为对于第一个也是最后一个列,这会有一些缺陷。第一列将以" 开头,最后一列以" 结尾
                  • 某些程序返回的 CSV 文件具有不同的分隔符,因此可能需要相应地更改正则表达式。分号分隔符示例:awk -F "\"*;\"*" '{print $2}' textfile.csv
                  猜你喜欢
                  • 2023-04-04
                  • 1970-01-01
                  • 2019-10-14
                  • 1970-01-01
                  • 2023-03-02
                  • 1970-01-01
                  • 2021-06-24
                  • 2018-04-20
                  • 1970-01-01
                  相关资源
                  最近更新 更多