bash thêm / nối các cột mới từ các tệp khác
Tôi có một tệp name.txt của một cột, ví dụ:
A
B
C
D
E
F
Sau đó, tôi có nhiều tệp, vdxt, y.txt và z.txt
x.txt có
A 1
C 3
D 2
y.txt có
A 1
B 4
E 3
z.txt có
B 2
D 2
F 1
Đầu ra mong muốn là (điền vào 0 nếu không có ánh xạ)
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
Có thể làm cho nó với bash? (có lẽ awk?)
Cảm ơn nhiều !!!
lần chỉnh sửa đầu tiên - những nỗ lực dự kiến của tôi
Vì tôi còn khá mới đối với bash nên tôi thực sự khó tìm ra giải pháp khả thi với awk. Tôi quen thuộc hơn với R, trong đó điều này có thể được thực hiện bằng
namematrix[namematrix[,1]==xmatrix[,1],]
Nói chung, tôi thực sự đánh giá cao sự giúp đỡ tận tình bên dưới giúp tôi tìm hiểu thêm về awkvà join!
Chỉnh sửa lần thứ hai - một cách tiếp cận siêu hiệu quả đã được tìm ra!
May mắn thay, được truyền cảm hứng bởi một số câu trả lời thực sự tuyệt vời bên dưới, tôi đã sắp xếp ra một cách tính toán rất hiệu quả như bên dưới. Điều này có thể hữu ích cho những người khác gặp phải các câu hỏi tương tự, đặc biệt nếu họ xử lý số lượng tệp rất lớn với kích thước rất lớn.
Đầu tiên hãy chạm vào một join_awk.bash
#!/bin/bash
join -oauto -e0 -a1 $1 $2 | awk '{print $2}'
Ví dụ: thực thi tập lệnh bash này cho name.txt và x.txt
join_awk.bash name.txt x.txt
sẽ tạo ra
1
0
3
2
0
0
Lưu ý rằng ở đây tôi chỉ giữ lại cột thứ hai để tiết kiệm dung lượng đĩa, vì trong tập dữ liệu của tôi, các cột đầu tiên là những tên rất dài sẽ chiếm dung lượng đĩa rất lớn.
Sau đó, chỉ cần thực hiện
parallel join_awk.bash name.txt {} \> outdir/output.{} ::: {a,b,c}.txt
Điều này được lấy cảm hứng từ câu trả lời tuyệt vời bên dưới bằng cách sử dụng song song GNU và tham gia. Sự khác biệt là các câu trả lời dưới đây có chỉ định j1cho paralleldo logic phụ thêm serial của nó, mà làm cho nó không thực sự "song song". Ngoài ra, tốc độ sẽ ngày càng chậm hơn khi tiếp tục nối tiếp. Ngược lại, ở đây chúng ta thao tác song song từng tệp riêng biệt. Nó có thể cực kỳ nhanh khi chúng tôi xử lý số lượng lớn các tệp có kích thước lớn với nhiều CPU.
Cuối cùng, chỉ cần hợp nhất tất cả các tệp đầu ra một cột với nhau bằng cách
cd outdir
paste output* > merged.txt
Điều này cũng sẽ rất nhanh vì pastevốn dĩ là song song.
Trả lời
Bạn có thể sử dụng cái này awk:
awk 'NF == 2 {
map[FILENAME,$1] = $2
next
}
{
printf "%s", $1 for (f=1; f<ARGC-1; ++f) printf "%s", OFS map[ARGV[f],$1]+0
print ""
}' {x,y,z}.txt name.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
Thêm một cách làm nữa. Bạn có thể vui lòng thử làm theo, viết và thử nghiệm với các mẫu được hiển thị. IMHO nên hoạt động trong bất kỳ awkphiên bản nào , mặc dù tôi chỉ có phiên bản GNU 3.1 awk. Đây là cách rất đơn giản và thông thường, tạo một mảng trong lần đọc Input_file đầu tiên (chính), sau đó trong mỗi tệp thêm 0bất kỳ phần tử nào của mảng đó KHÔNG được tìm thấy trong Input_file cụ thể đó, chỉ được thử nghiệm với các mẫu nhỏ nhất định.
awk '
function checkArray(array){
for(i in array){
if(!(i in found)){ array[i]=array[i] OFS "0" }
}
}
FNR==NR{
arr[$0] next } foundCheck && FNR==1{ checkArray(arr) delete found foundCheck="" } { if($1 in arr){
arr[$1]=(arr[$1] OFS $2) found[$1]
foundCheck=1
next
}
}
END{
checkArray(arr)
for(key in arr){
print key,arr[key]
}
}
' name.txt x.txt y.txt z.txt
Giải thích: Thêm giải thích chi tiết cho trên.
awk ' ##Starting awk program from here.
function checkArray(array){ ##Creating a function named checkArray from here.
for(i in array){ ##CTraversing through array here.
if(!(i in found)){ array[i]=array[i] OFS "0" } ##Checking condition if key is NOT in found then append a 0 in that specific value.
}
}
FNR==NR{ ##Checking condition if FNR==NR which will be TRUE when names.txt is being read.
arr[$0] ##Creating array with name arr with index of current line. next ##next will skip all further statements from here. } foundCheck && FNR==1{ ##Checking condition if foundCheck is SET and this is first line of Input_file. checkArray(arr) ##Calling function checkArray by passing arr array name in it. delete found ##Deleting found array to get rid of previous values. foundCheck="" ##Nullifying foundCheck here. } { if($1 in arr){ ##Checking condition if 1st field is present in arr.
arr[$1]=(arr[$1] OFS $2) ##Appening 2nd field value to arr with index of $1.
found[$1] ##Adding 1st field to found as an index here.
foundCheck=1 ##Setting foundCheck here.
next ##next will skip all further statements from here.
}
}
END{ ##Starting END block of this program from here.
checkArray(arr) ##Calling function checkArray by passing arr array name in it.
for(key in arr){ ##Traversing thorugh arr here.
print key,arr[key] ##Printing index and its value here.
}
}
' name.txt x.txt y.txt z.txt ##Mentioning Input_file names here.
Vâng, bạn có thể làm được, và vâng, awklà công cụ. Sử dụng mảng và số dòng tệp bình thường của bạn (số FNR tệp bản ghi ) và tổng số dòng ( NR bản ghi ), bạn có thể đọc tất cả các chữ cái names.txttrong a[]mảng, sau đó theo dõi số tệp trong biến fno, bạn có thể thêm tất cả các phép cộng từ x.txtđó trước khi xử lý dòng đầu tiên của tệp tiếp theo ( y.txt), hãy lặp lại tất cả các chữ cái được nhìn thấy trong tệp cuối cùng và đối với những chữ cái không thấy ở vị trí a 0, sau đó tiếp tục xử lý như bình thường. Lặp lại cho mỗi tệp bổ sung.
Giải thích từng dòng thêm được hiển thị trong các nhận xét:
awk '
FNR==NR { # first file
a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a { # if line begins with letter in array
a[$1] = a[$1]" "$2 # append second field seen[$1]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}' name.txt x.txt y.txt z.txt
Ví dụ Sử dụng / Đầu ra
Chỉ cần sao chép ở trên và di chuột giữa vào một xterm với thư mục hiện tại chứa các tệp của bạn và bạn sẽ nhận được:
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
Tạo một kịch bản tự chứa
Nếu bạn muốn tạo một tập lệnh để chạy thay vì dán vào dòng lệnh, bạn chỉ cần bao gồm nội dung (không bao quanh trong dấu ngoặc đơn) và sau đó làm cho tệp có thể thực thi được. Ví dụ: bạn bao gồm trình thông dịch ở dòng đầu tiên và nội dung như sau:
#!/usr/bin/awk -f
FNR==NR { # first file
a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a { # if line begins with letter in array
a[$1] = a[$1]" "$2 # append second field seen[$1]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}
awk sẽ xử lý các tên tệp được cung cấp dưới dạng đối số theo thứ tự đã cho.
Ví dụ Sử dụng / Đầu ra
Sử dụng tệp script (tôi đã đặt nó vào names.awkvà sau đó được sử dụng chmod +x names.awkđể làm cho nó có thể thực thi được), sau đó bạn sẽ làm:
$ ./names.awk name.txt x.txt y.txt z.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
Hãy cho tôi biết nếu bạn có thêm câu hỏi.
Một cách tiếp cận khác với GNU awk
$ cat script.awk NF == 1 { name[$1] = $1 for (i = 1; i < ARGC - 1; i++) { name[$1] = name[$1] " 0" } next } { name[$1] = gensub(/ ./, " " $2, ARGIND - 1, name[$1])
}
END {
for (k in name) {
print name[k]
}
}
Gọi tập lệnh:
$ awk -f script.awk name.txt {x,y,z}.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
Đầu ra hiển thị cùng một thứ tự name.txt, nhưng tôi không nghĩ rằng điều đó sẽ đúng cho tất cả các loại đầu vào.
Điều này có thể phù hợp với bạn (GNU song song và tham gia):
cp name.txt out && t=$(mktemp) && parallel -j1 join -oauto -e0 -a1 out {} \> $t \&\& mv $t out ::: {x,y,z}.txt
Đầu ra sẽ ở trong tệp out.
Bạn có thể dùng join
join -a1 -e0 -o '0,2.2' name.txt x.txt | join -a1 -e0 -o '0,1.2,2.2' - y.txt | join -a1 -e0 -o '0,1.2,1.3,2.2' - z.txt
Với bashlàm thế nào về:
#!/bin/bash
declare -A hash # use an associative array
for f in "x.txt" "y.txt" "z.txt"; do # loop over these files
while read -r key val; do # read key and val pairs
hash[$f,$key]=$val # assign the hash to val done < "$f"
done
while read -r key; do
echo -n "$key" # print the 1st column for f in "x.txt" "y.txt" "z.txt"; do # loop over the filenames echo -n " ${hash[$f,$key]:-0}" # print the associated value or "0" if undefined
done
echo # put a newline
done < "name.txt"