2017-11-07 35 views
5

Tôi có một tệp có 2 cột và tôi muốn sử dụng các giá trị từ cột thứ hai để đặt phạm vi trong lệnh cắt để chọn phạm vi ký tự từ một tệp khác. Phạm vi mong muốn của tôi là ký tự ở vị trí của giá trị trong cột thứ hai cộng với 10 ký tự tiếp theo. Tôi sẽ đưa ra một ví dụ trong một thời gian.Cách lặp một dãy biến trong lệnh cắt

file của tôi là một cái gì đó như thế:

file với 2 cột và không có dòng trống giữa các dòng (file1.txt):

NAME1 10 
NAME2 25 
NAME3 48 
NAME4 66 

tập tin mà tôi muốn trích xuất phạm vi biến ký tự (chỉ một dòng rất dài không có dấu cách và không có phông chữ đậm) (file2.txt):

GATCGAGC GG GATTCTTTTT TTTTA GGCGAGTCAG CTAGCATCAGCTA CGAGAGGCGA GGGCGGGC TATCACGACT ACGACTACGACTACAGCATCAGCATCAGCGCACTAGAGCGAGGCTAGCTAGCTACGACTACGATCAGCATCGCACATCGACTACGATCAGCATCAGCTACGCATCGAAGAGAGAGC

... hoặc, theo nghĩa đen hơn (ví copy/paste để kiểm tra):

GATCGAGCGGGATTCTTTTTTTTTAGGCGAGTCAGCTAGCATCAGCTACGAGAGGCGAGGGCGGGCTATCACGACTACGACTACGACTACAGCATCAGCATCAGCGCACTAGAGCGAGGCTAGCTAGCTACGACTACGATCAGCATCGCACATCGACTACGATCAGCATCAGCTACGCATCGAAGAGAGAGC 

mong muốn dẫn đến tập tin, một chuỗi trên mỗi dòng (result.txt):

GATTCTTTTT 
GGCGAGTCAG 
CGAGAGGCGA 
TATCACGACT 

Tệp kết quả sẽ có các ký tự từ 10-20, 25-35, 48-58 và 66-76, mỗi ký tự trong một dòng mới. Vì vậy, nó sẽ luôn luôn giữ phạm vi 10, nhưng ở các điểm bắt đầu khác nhau và những điểm bắt đầu được thiết lập bởi các giá trị trong cột thứ hai từ tập tin đầu tiên.

Tôi đã thử các lệnh:

for i in $(awk '{print $2}' file1.txt); 
do 
     p1=$i; 
     p2=`expr "$1" + 10` 
     cut -c$p1-$2 file2.txt > result.txt; 
done 

tôi không nhận được bất kỳ kết quả hoặc thông báo lỗi.

Tôi cũng đã cố gắng:

while read line; do 
    set $line 
    p2=`expr "$2" + 10` 
    cut -c$2-$p2 file2.txt > result.txt; 
done <file1.txt 

lệnh cuối cùng này mang lại cho tôi một thông báo lỗi:

cut: invalid range with no endpoint: - 
Try 'cut --help' for more information. 
expr: non-integer argument 
+0

Đó là một câu hỏi rất tốt vì là Đầu tiên. Rõ ràng và nó cho thấy nỗ lực. – klutt

+0

Hãy nhớ chấp nhận câu trả lời. – klutt

Trả lời

4

Không cần cho cut đây; dd có thể thực hiện công việc lập chỉ mục vào một tệp và chỉ đọc số byte bạn muốn. (Lưu ý rằng status=none là một GNUism; bạn có thể cần phải loại bỏ nó trên các nền tảng khác và chuyển hướng stderr nếu không, nếu bạn muốn ngăn chặn việc ghi nhật ký thông tin).

while read -r name index _; do 
    dd if=file2.txt bs=1 skip="$index" count=10 status=none 
    printf '\n' 
done <file1.txt >result.txt 

Cách tiếp cận này tránh yêu cầu bộ nhớ quá mức (như hiện nay khi đọc toàn bộ file2 - giả định đó là lớn), và đã bị chặn yêu cầu hiệu suất (overhead bằng để bắt đầu một bản sao của dd mỗi chuỗi để giải nén).

+0

Đề xuất tuyệt vời! Cảm ơn nhiều. Nó hoạt động hoàn hảo. –

0

Một cách để giải quyết nó:

#!/bin/bash                           

while read line; do 
    pos=$(echo "$line" | cut -f2 -d' ') 
    x=$(head -c $(($pos + 10)) file2.txt | tail -c 10) 
    echo "$x" 
done <file1.txt> result.txt 

Nó không phải là giải pháp một bash của hacker có kinh nghiệm sẽ sử dụng, nhưng nó là rất tốt cho một người mới để bash. Nó sử dụng các công cụ rất linh hoạt, mặc dù hơi xấu nếu bạn cần hiệu suất cao. Shell kịch bản thường được sử dụng bởi những người hiếm khi shell script, nhưng biết một vài lệnh và chỉ muốn có được công việc làm. Đó là lý do tại sao tôi bao gồm giải pháp này, ngay cả khi các câu trả lời khác là tốt hơn cho những người có kinh nghiệm hơn.

Dòng đầu tiên khá dễ dàng. Nó chỉ trích xuất các số từ file1.txt. Dòng thứ hai sử dụng các công cụ rất đẹp headtail. Thông thường, chúng được sử dụng với các dòng thay vì ký tự. Tuy nhiên, tôi in các ký tự đầu tiên pos + 10 với head. Kết quả được chuyển thành tail để in các ký tự 10 cuối cùng.

Nhờ @CharlesDuffy để cải thiện.

+0

(Tôi cũng khuyên bạn nên tránh subshells trong một vòng lặp bên trong, * đặc biệt là * khi họ là tầm thường tránh được, mỗi '$ (...)' là một 'fork()' và 'wait()'). –

+0

Vì tôi là một newbie bash, tôi thực sự đánh giá cao giải pháp của bạn. Nó có ý nghĩa với tôi. Cảm ơn bạn! –

2

Nếu file2.txt không phải là quá lớn, sau đó bạn có thể đọc nó trong bộ nhớ, và sử dụng Bash tiểu chuỗi để trích xuất phạm vi mong muốn:

data=$(<file2.txt) 
while read -r name index _; do 
    echo "${data:$index:10}" 
done <file1.txt >result.txt 

Đây sẽ là hiệu quả hơn so với chạy cut hoặc một quy trình khác cho mọi định nghĩa phạm vi đơn lẻ.

(Nhờ @CharlesDuffy for the tip để đọc datamà không một vô dụng cat, và while vòng lặp.)

+2

'data = $ (

+2

file2.txt là một bộ gen nhân chuẩn hoàn toàn, do đó, nó là một tệp lớn, nhưng giải pháp của bạn là tuyệt vời cho bộ gen nhỏ, giống như những bộ phận sinh sản. Cảm ơn bạn đã gợi ý. –

3

Sử dụng awk

$ awk 'FNR==NR{a=$0; next} {print substr(a,$2+1,10)}' file2 file1 
GATTCTTTTT 
GGCGAGTCAG 
CGAGAGGCGA 
TATCACGACT 
+1

Hmm. Điều này lưu trữ tất cả 'file2' trong bộ nhớ, phải không? Vì vậy, có vẻ như nó sẽ là một giải pháp tốt nếu file1 dài (kể từ khi «awk' loop nhanh hơn nhiều so với bash' trong khi read' loop hiện), nhưng không quá nhiều nếu file2 dài (ngoài những gì có thể phù hợp với RAM) . –

+1

@CharlesDuffy Khi tệp2 dài 'dữ liệu = (

+0

Vâng, tôi đồng ý - đó là lý do tại sao tôi nhận xét rằng tôi thích giải pháp "nếu file2.txt nhỏ/ngắn" (và tác giả của nó đã giới hạn rõ ràng trong văn xuôi xung quanh) và tại sao tôi nghĩ giải pháp của riêng mình có nơi đó là lựa chọn tốt nhất (nếu data2 có khả năng quá lớn để lưu trữ trong RAM). –

Các vấn đề liên quan