Cách trích xuất một cột của tệp csv


111

Nếu tôi có tệp csv, có cách nào nhanh chóng để in ra nội dung chỉ của bất kỳ cột đơn lẻ nào không? Có thể an toàn khi giả định rằng mỗi hàng có cùng số cột, nhưng nội dung của mỗi cột sẽ có độ dài khác nhau.

Câu trả lời:


135

Bạn có thể sử dụng awk cho việc này. Thay đổi '$ 2' thành cột thứ n mà bạn muốn.

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

13
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'sẽ in 2thay vì 2,3,4,5.
Igor Mikushkin

Nếu bạn là một anh chàng may mắn sử dụng GNU Tools trong Windows, bạn có thể thực hiện tương tự như COMAND @IgorMikushkin như sau:gawk -F"|" "{print $13}" files*.csv
Elidio Marquina

10
Tôi nghĩ rằng đây không thành công khi có chuỗi có chứa một dấu phẩy, ví dụ:...,"string,string",...
natri nitrat

Tôi nghĩ rằng đối với cột đầu tiên và cuối cùng, điều này sẽ có một số sai sót. Cột đầu tiên sẽ bắt đầu với "và cuối cùng sẽ kết thúc với"
BigTailWolf

Một số chương trình trả về tệp CSV với các dấu phân cách khác nhau, vì vậy có thể yêu cầu thay đổi biểu thức chính quy cho phù hợp. Ví dụ về dấu phân cách bằng dấu chấm phẩy: awk -F "\"*;\"*" '{print $2}' textfile.csv
gekkedev

88

Đúng. cat mycsv.csv | cut -d ',' -f3sẽ in cột thứ 3.


8
Trừ khi cột hai chứa dấu phẩy, trong trường hợp đó bạn sẽ nhận được nửa sau của cột hai. Trường hợp trong điểm <col1>, "3.000", <col2>. Câu trả lời của tôi không tốt hơn nhiều đối với vấn đề đó. Vì vậy, không được ăn mày.
synthesizerpatel

@synthesizerpatel Tôi đồng ý tốt hơn để sử dụngawk
MattSizzle

1
Chúng tôi không chắc rằng tệp CSV của anh ấy chứa các dấu ngoặc kép để phân biệt các giá trị khác biệt. Sẽ tốt hơn nếu anh ấy cung cấp một tập tin đầu vào để chúng tôi có thể đánh giá giải pháp phù hợp nhất.
Idriss Neumann

50

Cách đơn giản nhất mà tôi có thể thực hiện là chỉ sử dụng csvtool . Tôi cũng có các trường hợp sử dụng khác để sử dụng csvtool và nó có thể xử lý các dấu ngoặc kép hoặc dấu phân cách một cách thích hợp nếu chúng xuất hiện trong chính dữ liệu cột.

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

Thay thế 2 bằng số cột sẽ trích xuất dữ liệu cột bạn đang tìm kiếm một cách hiệu quả.


14
Đây phải là câu trả lời được chấp nhận. Công cụ này biết cách xử lý các tệp CSV, ngoài việc coi dấu phẩy là dấu phân cách trường. Để giải nén cột thứ 2, "csvtool col 2 input.csv"
Vladislavs Dovgalecs

3
Chỉ cần lưu ý ... nếu bạn muốn sử dụng csvtool với đầu vào tiêu chuẩn (ví dụ csv đến từ một lệnh khác) thì nó giống như thế này cat input.csv | csvtool formath '%(2)\n' -Lưu ý Tôi biết mèo ở đây là vô dụng nhưng hãy phụ nó cho bất kỳ lệnh nào thường xuất ra một csv.
General Redneck

Nó có các trường nhiều dòng, format '%(2)\n'lệnh không thể cho biết một trường kết thúc ở đâu. (csvtool 1.4.2)
jarno

1
Các phiên bản mới hơn của csvtooldường như yêu cầu sử dụng -làm tên tệp đầu vào để đọc từ stdin.
Connor Clark

@GeneralRedneck tại sao lại sử dụng mèo? và nó không phải là định dạngcsvtool format '%(1),%(10)\n' - < in.csv > out.csv
sijanec

14

Hạ cánh ở đây để giải nén từ một tệp được phân tách bằng tab. Tôi nghĩ rằng tôi sẽ thêm.

cat textfile.tsv | cut -f2 -s

Trong đó -f2trích xuất cột được lập chỉ mục 2, khác 0 hoặc cột thứ hai.


đơn giản, quá quan trọng và dễ dàng điều chỉnh hơn các ví dụ khác. cảm ơn!
Nick Jennings

6
Nitpicking, nhưng catlà không cần thiết:< textfile.tsv cut -f2 -s
Anne van Rossum

8

Nhiều câu trả lời cho câu hỏi này là tuyệt vời và một số thậm chí đã xem xét các trường hợp góc. Tôi muốn thêm một câu trả lời đơn giản có thể được sử dụng hàng ngày ... nơi bạn hầu hết mắc phải những trường hợp góc cạnh đó (như thoát dấu phẩy hoặc dấu phẩy trong dấu ngoặc kép, v.v.).

FS (Field Separator) là biến có giá trị được mặc định là không gian. Vì vậy, theo mặc định, awk sẽ phân chia theo không gian cho bất kỳ dòng nào.

Vì vậy, bằng cách sử dụng BEGIN (Thực hiện trước khi nhận đầu vào), chúng ta có thể đặt trường này thành bất kỳ thứ gì chúng ta muốn ...

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

Đoạn mã trên sẽ in cột thứ 3 trong tệp csv.


1
Tôi đã thử điều này và nó vẫn coi là dấu phẩy bên trong các trường được trích dẫn.
Daniel C. Sobral

5

Các câu trả lời khác hoạt động tốt, nhưng vì bạn đã yêu cầu giải pháp chỉ sử dụng bash shell, bạn có thể thực hiện điều này:

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

Và sau đó bạn có thể kéo ra các cột (cột đầu tiên trong ví dụ này) như sau:

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

Vì vậy, có một số điều đang diễn ra ở đây:

  • while IFS=,- đây là ý nói sử dụng dấu phẩy làm IFS (Internal Field Separator), là thứ mà shell sử dụng để biết những gì phân tách các trường (khối văn bản). Vì vậy, nói IFS =, giống như nói "a, b" cũng giống như "a b" sẽ là nếu IFS = "" (theo mặc định).

  • read -a csv_line; - đây là nói đọc từng dòng, từng dòng một và tạo một mảng trong đó mỗi phần tử được gọi là "csv_line" và gửi phần tử đó đến phần "do" trong vòng lặp while của chúng tôi

  • do echo "${csv_line[0]}";done < file- bây giờ chúng ta đang ở giai đoạn "do" và chúng ta đang nói echo phần tử thứ 0 của mảng "csv_line". Hành động này được lặp lại trên mọi dòng của tệp. Phần < filenày chỉ cho biết vòng lặp while cần đọc từ đâu. LƯU Ý: hãy nhớ rằng, trong bash, mảng có 0 được lập chỉ mục, vì vậy cột đầu tiên là phần tử thứ 0.

Vì vậy, bạn có nó, kéo ra một cột từ CSV trong shell. Các giải pháp khác có lẽ thực tế hơn, nhưng giải pháp này là thuần túy.


5

Bạn có thể sử dụng GNU Awk, hãy xem bài viết này của hướng dẫn sử dụng . Là một cải tiến cho giải pháp được trình bày trong bài báo (vào tháng 6 năm 2015), lệnh gawk sau đây cho phép dấu ngoặc kép bên trong các trường được trích dẫn kép; một dấu ngoặc kép được đánh dấu bằng hai dấu ngoặc kép liên tiếp ("") ở đó. Hơn nữa, điều này cho phép các trường trống, nhưng ngay cả điều này cũng không thể xử lý các trường đa dòng . Ví dụ sau in cột thứ 3 (qua c=3) của textfile.csv:

#!/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)

Lưu ý việc sử dụng dos2unixđể chuyển đổi các ngắt dòng kiểu DOS có thể có (CRLF tức là "\ r \ n") và mã hóa UTF-16 (có dấu thứ tự byte) thành "\ n" và UTF-8 (không có dấu thứ tự byte), tương ứng. Tệp CSV tiêu chuẩn sử dụng CRLF làm ngắt dòng, xem Wikipedia .

Nếu đầu vào có thể chứa các trường nhiều dòng, bạn có thể sử dụng tập lệnh sau. Lưu ý việc sử dụng chuỗi đặc biệt để phân tách các bản ghi trong đầu ra (vì dòng mới phân tách mặc định có thể xảy ra trong một bản ghi). Một lần nữa, ví dụ sau in cột thứ 3 (thông qua c=3) của 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)

Có một cách tiếp cận vấn đề khác. csvquote có thể xuất nội dung của tệp CSV được sửa đổi để các ký tự đặc biệt trong trường được chuyển đổi để có thể sử dụng các công cụ xử lý văn bản Unix thông thường để chọn cột nhất định. Ví dụ, đoạn mã sau xuất ra cột thứ ba:

csvquote textfile.csv | cut -d ',' -f 3 | csvquote -u

csvquote có thể được sử dụng để xử lý các tệp lớn tùy ý.


5

Đây là một ví dụ về tệp csv có 2 cột

myTooth.csv

Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom

Để lấy cột đầu tiên, hãy sử dụng:

cut -d, -f1 myTooth.csv

f là viết tắt của Field và d là viết tắt của dấu phân cách

Chạy lệnh trên sẽ tạo ra kết quả sau.

Đầu ra

Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28

Để chỉ lấy cột thứ 2:

cut -d, -f2 myTooth.csv

Và đây là đầu ra Output

Tooth
wisdom
canine
canine
wisdom
incisor

Một trường hợp sử dụng khác:

Tệp đầu vào csv của bạn chứa 10 cột và bạn muốn các cột từ 2 đến 5 và cột 8, sử dụng dấu phẩy làm dấu phân tách ".

cut sử dụng -f (nghĩa là "trường") để chỉ định cột và -d (nghĩa là "dấu phân cách") để chỉ định dấu phân tách. Bạn cần chỉ định cái sau vì một số tệp có thể sử dụng dấu cách, tab hoặc dấu hai chấm để phân tách các cột.

cut -f 2-5,8 -d , myvalues.csv

cut là một tiện ích lệnh và đây là một số ví dụ khác:

SYNOPSIS
     cut -b list [-n] [file ...]
     cut -c list [file ...]
     cut -f list [-d delim] [-s] [file ...]

4

Tôi cần phân tích cú pháp CSV thích hợp, không phải cut/ awkvà cầu nguyện. Tôi đang thử điều này trên máy Mac không có csvtool, nhưng máy Mac đi kèm với ruby, vì vậy bạn có thể làm:

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

4

Đầu tiên, chúng tôi sẽ tạo một CSV cơ bản

[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

Sau đó, chúng tôi nhận được cột đầu tiên

[dumb@one pts]$  awk -F , '{print $1}' file  
a  
1  
a  
1

3
csvtool col 2 file.csv 

trong đó 2 là cột bạn quan tâm

bạn cũng có thể làm

csvtool col 1,2 file.csv 

để làm nhiều cột


3

Tôi nghĩ đơn giản nhất là sử dụng csvkit :

Lấy cột thứ 2: csvcut -c 2 file.csv

Tuy nhiên, cũng có csvtool và có thể là một số công cụ bash csv khác ngoài đó:

sudo apt-get install csvtool (dành cho hệ thống dựa trên Debian)

Điều này sẽ trả về một cột có hàng đầu tiên có 'ID' trong đó. csvtool namedcol ID csv_file.csv

Điều này sẽ trả về hàng thứ tư: csvtool col 4 csv_file.csv

Nếu bạn muốn bỏ hàng tiêu đề:

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


2

Tôi tự hỏi tại sao không có câu trả lời nào cho đến nay đã đề cập đến csvkit.

csvkit là một bộ công cụ dòng lệnh để chuyển đổi sang và làm việc với CSV

tài liệu csvkit

Tôi sử dụng nó riêng để quản lý dữ liệu csv và cho đến nay tôi vẫn chưa tìm thấy sự cố mà tôi không thể giải quyết bằng cvskit.

Để trích xuất một hoặc nhiều cột từ tệp cvs, bạn có thể sử dụng csvcuttiện ích là một phần của hộp công cụ. Để trích xuất cột thứ hai, hãy sử dụng lệnh này:

csvcut -c 2 filename_in.csv > filename_out.csv 

trang tham khảo csvcut

Nếu các chuỗi trong csv được trích dẫn, hãy thêm ký tự trích dẫn với qtùy chọn:

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

Cài đặt bằng pip install csvkithoặc sudo apt install csvkit.


1

Bạn không thể làm điều đó nếu không có trình phân tích cú pháp CSV đầy đủ.


1
Khi nào thứ gì đó được coi là trình phân tích cú pháp CSV đầy đủ? Có cuttính không?
HelloGoodbye

0

Đã sử dụng mã này một thời gian, nó không phải là "nhanh" trừ khi bạn tính "cắt và dán từ stackoverflow".

Nó sử dụng các toán tử $ {##} và $ {%%} trong một vòng lặp thay vì IFS. Nó gọi 'err' và 'die', và chỉ hỗ trợ dấu phẩy, dấu gạch ngang và dấu gạch dưới dạng ký tự SEP (đó là tất cả những gì tôi cần).

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*}
}

Thí dụ:

$ 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

0

Bạn cũng có thể sử dụng vòng lặp while

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

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

Mã này tạo ra cảnh báo Shellcheck : SC2034 . Tìm kiếm trả về câu hỏi này dưới dạng kết quả đầu tiên khi tìm cách bỏ qua cảnh báo.
jww
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.