Làm cách nào tôi có thể áp dụng `cut` cho một số tệp và sau đó 'dán` kết quả?


8

Tôi thường làm các hoạt động như

paste <(cut -d, -f1 file1.csv) <(cut -d, -f1 file2.csv)

đó là rất tẻ nhạt với nhiều hơn một vài tập tin.

Tôi có thể tự động hóa quá trình này, ví dụ như với Globing? Tôi có thể lưu cutkết quả với

typeset -A cut_results
for f in file*.csv; do
    cut_results[$f]="$(cut -d, -f1 $f)"
done

nhưng tôi không chắc làm thế nào để tiến hành từ đó.


github.com/thrig/sial.org-scripts/blob/master/misc/stitch là những gì tôi sử dụng cho một cái gì đó như nhiệm vụ này.
thrig

Đôi khi nếu không. các trường / cột được biết đến, bạn cũng có thể dán tất cả các tệp và sau đó cắt các trường bạn cần ...
don_crissti

mèo | cắt | dán?
bot47

@MaxRied mà không làm những gì muốn.
Shadowtalker

Sau đó tôi không hiểu câu hỏi của bạn. Không cat file*.csv | cut -d, -f1 | pastelàm những gì bạn cố gắng lưu trữ?
bot47

Câu trả lời:


4

Bạn có thể tự động hoá này với globbing, đặc biệt là e vòng loại glob , cộng thêm eval, nhưng nó không phải là khá và trích dẫn là khó khăn:

eval paste *.csv(e\''REPLY="<(cut -d, -f1 $REPLY)"'\')
  • Phần giữa \'…\'là một số mã để thực thi cho mọi trận đấu trên toàn cầu. Nó được thực thi với biến REPLYđược đặt thành khớp và có thể sửa đổi nó.
  • Tôi đặt mã trong dấu ngoặc đơn để nó không được mở rộng khi toàn cầu được phân tích cú pháp.
  • REPLY="<(cut -d, -f1 $REPLY)"tạo ra chuỗi <(cut -d, -f1 file1.csv)nếu khớp file1.csv. Dấu ngoặc kép là cần thiết để phần sau dấu bằng không được mở rộng khi emã được thực thi ngoài việc thay thế giá trị của REPLY.
  • Vì mỗi tệp toàn cầu được thay thế bằng một chuỗi

Nó sẽ đẹp hơn để che giấu sự phức tạp trong một chức năng. Thử nghiệm tối thiểu.

function map {
  emulate -LR zsh
  local cmd pre
  cmd=()
  while [[ $# -ne 0 && $1 != "--" ]]; do
    cmd+=($1)
    shift
  done
  if ((!$#)); then
    echo >&2 "Usage: $0: COMMAND [ARGS...] -- PREPROCESSOR [ARGS...] -- FILES..."
    return 125
  fi
  shift
  while [[ $# -ne 0 && $1 != "--" ]]; do
    pre+="${(q)1} "
    shift
  done
  if ((!$#)); then
    echo >&2 "Usage: $0: COMMAND [ARGS...] -- PREPROCESSOR [ARGS...] -- FILES..."
    return 125
  fi
  shift
  eval "${(@q)cmd}" "<($pre${(@q)^@})"
}

Sử dụng mẫu (cú pháp gợi nhớ lại zargs):

map paste -- cut -d, -f1 -- *.csv

Mẹo hay về evòng loại và ý tưởng tuyệt vời bao bọc nó trong chức năng "ánh xạ" chung.
Shadowtalker

Tất nhiên, điều này gây nghẹt thở khi bạn phải chuyển qua --làm đối số cho một trong các tiện ích nhưng tôi không nghĩ rằng tôi sẽ gặp phải trường hợp đó
Shadowtalker

Một giải pháp sẽ là sao chép findcú pháp của nó, sử dụng dấu chấm phẩy thoát vỏ để kết thúc lệnh và chuỗi giữ chỗ cho đối số
Shadowtalker

4

Tôi nghĩ rằng dòng đầu tiên của bạn là tốt như nó được cho một lót đơn giản.

Nếu có một loạt các tệp có tất cả các tên khác nhau, bạn có thể giảm việc gõ lặp đi lặp lại một chút với một "cheat" mở rộng lịch sử đơn giản:

Lần chạy đầu tiên <(cut -d, -f1

Lưu ý không gian dấu. Cũng lưu ý rằng lệnh này sẽ cung cấp cho bạn một dấu nhắc phụ; chỉ cần nhấn Ctrl- C. Điểm duy nhất là thêm nó vào lịch sử.

Lần chạy tiếp theo paste !!file1.csv) !!file2.csv)

Các !!sẽ mở rộng đến toàn bộ nội dung của chạy lệnh trước đó, bao gồm cả không gian dấu. Lưu ý rằng nếu bạn quên dấu ngoặc đơn gần, bạn sẽ nhận được một dấu nhắc phụ; bạn chỉ có thể gõ Ctrl- Cvà thử lại nếu điều này xảy ra.

Đây là một chút hacky nhưng đủ tốt để sử dụng một lần. Nếu bạn đang làm điều đó nhiều, bạn có thể viết một hàm bash.


3

Hãy thử awk

awk '{L[FNR]=L[FNR] $1 "\t"}END{for(i=1;i<=FNR;i++)print L[i]}' *.csv

hoặc dán với sed

paste *.csv | sed 's/ [^\t]*//g'

Tôi luôn quên tôi có thể sử dụng AWK cho nhiều tệp. Đây chắc chắn là câu trả lời tốt nhất cho trường hợp sử dụng cụ thể của tôi, nhưng tôi chấp nhận câu trả lời "bản đồ" vì nó gần với những gì tôi nghĩ trong câu hỏi này.
Shadowtalker

1

Hiện tại tôi đang học bashkịch bản, và đây có vẻ là một nhiệm vụ đơn giản tuyệt vời để thực hành, vì vậy tôi đã viết như sau. (Câu trả lời khác của tôi cung cấp cho hack mở rộng lịch sử đơn giản, nhưng đây là một tập lệnh đầy đủ và tôi cho rằng nó xứng đáng để đưa ra một câu trả lời bổ sung.) Tôi tin rằng đây là tương thích POSIX và nên hoạt động #!/bin/sh, nhưng không chắc chắn 100%. EDIT: Trên thực tế, =~không tương thích POSIX. Tuy nhiên, bạn có thể kiểm tra và cuttrả lại lỗi.

#!/bin/bash

fieldtocut=1
delimiter=','

usage () {
    cat << EOF
usage: $0 [-f FIELD] [-d DELIMITER] file1..
Cuts field FIELD from each file and pastes it.
Default field is 1, default delimiter is ','
EOF
    exit $1
}

while getopts ':f:d:' opt ; do
    case $opt in
        f)
            if [[ $OPTARG =~ ^[0-9]+$ ]] ; then
                fieldtocut="$OPTARG"
            else
                usage 1
            fi
            ;;
        d)
            delimiter=$OPTARG
            ;;
        *)
            usage 1
            ;;
    esac
done
shift $((OPTIND-1))

[ $# -eq 0 ] && usage 0

pasteargs=''

for file in "$@" ; do
    pasteargs=$(printf '%s' "$pasteargs" '<(cut -d$delimiter -f$fieldtocut ' "$file" ') ')
done

eval paste $pasteargs

Tập lệnh của bạn sẽ thất bại nếu dấu phân cách (hoặc trường cần cắt) là ký tự đặc biệt shell, ví dụ ;hoặc tab hoặc nếu tên tệp chứa các ký tự đặc biệt shell.
Gilles 'SO- ngừng trở nên xấu xa'

Grrr. Tốt đốm. Mất dấu vết trích dẫn của tôi. Tôi đã thử một vài cách khác, nhưng tôi đã nhầm lẫn với trích dẫn và cuối cùng làm cho nó thậm chí ít khả thi hơn. Bất kỳ lời khuyên về nó, cho mục đích học tập? :)
tự đại diện

Trong zsh bạn chỉ có thể sử dụng ${(q)delimiter}. Nếu bạn muốn mã cũng hoạt động trong bash, thì khó hơn; Tôi nghĩ rằng quoted_single_quote=\'\\\'\'; delimiter="'${delimiter//'/"$quoted_single_quote"}'"hoạt động trong ksh93, bash và zsh.
Gilles 'SO- ngừng trở nên xấu xa'

1

Giả sử bạn đang tranh luận "$@", tôi tin điều gì đó như:

eval "paste $(printf "<( cut -d, -f1 %q ) " "$@")"

Hãy làm nó.


bạn có thể gặp vấn đề về độ dài dòng lệnh với cách tiếp cận, nhưng,
malcook

0

Đây là một cách khác để làm điều đó rất giống với câu trả lời của Wildcard :

files=( file1.csv file2.csv)
eval paste "<( cut -d, -f1 ${^files[@]} )"

Thay vì một forvòng lặp, điều này sử dụng việc ${^ ... }mở rộng dành riêng cho Zsh.

Lý do filesphải được chỉ định trước tiên là vì việc tạo hình cầu luôn luôn được thực hiện sau cùng, vì vậy nếu filescần được tạo tự động (như trong files=( *.csv )) thì một cái gì đó ${^:-( *.csv )}sẽ chỉ mở rộng sau khi tất cả các mở rộng khác đã xảy ra. Chúng tôi muốn nó mở rộng đầu tiên .

Việc ${^ ... }mở rộng làm cho mảng kết quả hoạt động giống như kết quả của việc mở rộng dấu ngoặc. Ví dụ, gán x=(a b)và sau đó so sánh echo ${x}yvới echo ${^x}y.

Việc trích dẫn là cần thiết để lừa Zsh đối xử với văn bản xung quanh như một chuỗi ký tự. Nếu không, nó sẽ phân chia dòng lệnh tại các khoảng trắng, vì vậy ${^ ... }việc mở rộng của chúng tôi sẽ giảm xuống ""${^ ... }""; nghĩa là, mỗi phần tử sẽ chỉ được bao quanh bởi một chuỗi rỗng. Đó là,

echo "<( cut -d, -f1 ${^files[@]} )"

echo "<( cut -d, -f1 "\
${^files[@]}\
" )"

là tương đương, nhưng không giống như

echo <( cut -d, -f1 ${^files[@]} )

Nhưng trích dẫn giới thiệu một vấn đề mới: dòng lệnh được phân tích cú pháp và phân tách mà không liên quan đến việc mở rộng đang diễn ra. Đó là, mặc dù chúng tôi đã nhập một cách hiệu quả

paste <( cut -d, -f1 file1.csv ) <( cut -d, -f1 file2.csv )

như mong muốn, thực tế đây là phân tích cú pháp như

paste '<( cut -d, -f1 file1.csv )' '<( cut -d, -f1 file2.csv )'

Do đó, chúng ta cần evalphân tích lại biểu thức được tạo chính xác. Để thấy điều này trong hành động, so sánh

setopt noxtrace
eval paste "<( cut -d, -f1 ${^files[@]} )" 1>/dev/null 2>&1

đến

setopt xtrace
eval paste "<( cut -d, -f1 ${^files[@]} )" 1>/dev/null 2>&1

Tôi hy vọng rằng một số sự kết hợp các hoạt động mở lồng nhau, các ${ ... :- ... }mở rộng, và những lá cờ mở rộng tham số Q, zvà / hoặc ssẽ dẫn đến đánh giá lại mà không eval, nhưng rõ ràng đó không phải là trường hợp. Tôi cũng ước có một cách để buộc toàn cầu, nhưng một lần nữa điều đó dường như là không thể.


0

Bạn có thể awklặp qua các tệp theo từng bước và báo cáo trường quan tâm từ mỗi tệp. Đặt mã này vào một tập tin, nóicut_files.awk

NR == FNR{printf "%s%s",$1, FS;
for (k=2; k<ARGC; ++k)
    {getline < ARGV[k]; printf "%s%s", $1, k==ARGC-1?"\n":FS}; next};
NR != FNR{for (k=2; k<ARGC; ++k) close(ARGV[k]); exit}

Và sau đó gọi nó như vậy

awk -F',' -f cut_files.awk file1 file2 file3 file4 ....
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.