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.
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:
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
gawk -F"|" "{print $13}" files*.csv
...,"string,string",...
"
và cuối cùng sẽ kết thúc với"
awk -F "\"*;\"*" '{print $2}' textfile.csv
Đúng. cat mycsv.csv | cut -d ',' -f3
sẽ in cột thứ 3.
awk
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ả.
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.
format '%(2)\n'
lệnh không thể cho biết một trường kết thúc ở đâu. (csvtool 1.4.2)
csvtool
dường như yêu cầu sử dụng -
làm tên tệp đầu vào để đọc từ stdin.
csvtool format '%(1),%(10)\n' - < in.csv > out.csv
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 đó -f2
trích xuất cột được lập chỉ mục 2, khác 0 hoặc cột thứ hai.
cat
là không cần thiết:< textfile.tsv cut -f2 -s
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.
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 < file
nà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.
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 ý.
Đâ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 ...]
Tôi cần phân tích cú pháp CSV thích hợp, không phải cut
/ awk
và 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
Đầ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
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'
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 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 csvcut
tiệ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
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 q
tùy chọn:
csvcut -q '"' -c 2 filename_in.csv > filename_out.csv
Cài đặt bằng pip install csvkit
hoặc sudo apt install csvkit
.
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 đủ.
cut
tính không?
Đã 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
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
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'
sẽ in2
thay vì2,3,4,5
.