Giao lộ của hai danh sách trong Bash


162

Tôi đang cố gắng viết một tập lệnh đơn giản sẽ liệt kê các nội dung được tìm thấy trong hai danh sách. Để đơn giản hóa, hãy sử dụng ls làm ví dụ. Hãy tưởng tượng "một" và "hai" là các thư mục.

một = `tôi là một
hai = `ls hai`
ngã tư $ một $ hai

Tôi vẫn còn khá xanh trong bash, vì vậy hãy thoải mái sửa cách tôi đang làm điều này. Tôi chỉ cần một số lệnh sẽ in ra tất cả các tệp trong "một" và "hai". Họ phải tồn tại trong cả hai. Bạn có thể gọi đây là "giao điểm" giữa "một" và "hai".


Không có gì ở đây thực sự trả lời câu hỏi: làm thế nào để giao nhau hai biến trong tập lệnh Bash.
jameshfisher

Có vẻ như một câu hỏi mới theo ý kiến ​​của tôi, câu hỏi đó được trả lời rõ ràng ở đây.
Jean-Barshe Meillaud

Một cách tiếp cận hữu ích hơn có thể được đưa ra là trong stackoverflow
lặp.com/questions/2312762/iêu

Câu trả lời:


284
comm -12  <(ls 1) <(ls 2)

37
Không thể tin rằng tôi không có kiến ​​thức commcho đến ngày hôm nay. Điều này chỉ làm cho cả tuần của tôi :)
Darragh Enright

22
commyêu cầu các đầu vào được sắp xếp. Trong trường hợp này, lstự động sắp xếp đầu ra của nó, nhưng các cách sử dụng khác có thể cần phải làm điều này:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird

11
KHÔNG SỬ DỤNG đầu ra của ls cho bất cứ điều gì. ls là một công cụ để tương tác nhìn vào siêu dữ liệu thư mục. Mọi nỗ lực phân tích đầu ra của ls với mã đều bị hỏng. Globs đơn giản hơn nhiều VÀ chính xác: '' cho tệp trong * .txt ''. Đọc mywiki.wooledge.org/ParsingLs
Rany Albeg Wein

2
Tôi chỉ sử dụng điều này trong một nỗ lực để tìm ra cách sử dụng một publicphương pháp error()được cung cấp bởi một đặc điểm, kết hợp với git grep, và nó thật tuyệt vời! Tôi đã chạy $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php"), và may mắn là cuối cùng tôi chỉ có tên của tập tin chứa đặc điểm đó.
localheinz

3
Đây là vui nhộn. Tôi đã cố gắng làm một số thứ điên rồ với awk.
Rolf

54

Giải pháp với comm

commlà tuyệt vời nhưng thực sự cần phải làm việc với danh sách được sắp xếp. Và may mắn thay ở đây chúng tôi sử dụng lstừ lstrang man Bash

Sắp xếp các mục theo thứ tự bảng chữ cái nếu không có -cftuSUX cũng như --sort.

comm -12  <(ls one) <(ls two)

Thay thế bằng sort

Giao lộ của hai danh sách:

sort <(ls one) <(ls two) | uniq -d

sự khác biệt đối xứng của hai danh sách:

sort <(ls one) <(ls two) | uniq -u

Tặng kem

Chơi với nó ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}

2
Thay vì bổ sung , tôi nghĩ đó là những gì thường được gọi là sự khác biệt đối xứng .
Andrew Lazarus

29

Sử dụng commlệnh:

ls one | sort > /tmp/one_list
ls two | sort > /tmp/two_list
comm -12 /tmp/one_list /tmp/two_list

"Sắp xếp" không thực sự cần thiết nhưng tôi luôn bao gồm nó trước khi sử dụng "comm" chỉ trong trường hợp.


5
Thật tốt khi bao gồm nó vì nó cần phải được sắp xếp, và anh ta chỉ sử dụng ls làm ví dụ.
Thor84no

3

Một thay thế kém hiệu quả (hơn comm):

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d

1
Nếu bạn đang sử dụng Debian / bin / dash hoặc một số shell không phải Bash khác trong tập lệnh của mình, bạn có thể xâu chuỗi đầu ra của lệnh bằng cách sử dụng dấu ngoặc đơn : (ls 1; ls 2) | sort -u | uniq -d.
nitơ

1
@ MikaëlMayer Bạn nên gắn cờ tên của người bạn đang trả lời, nếu không, giả sử bạn có nghĩa là tôi.
Benubird

@nitrogen MikaëlMayer là chính xác - chaing sort -u | uniq -dkhông làm gì cả, vì loại này đã loại bỏ các bản sao trước khi uniq bắt đầu tìm kiếm chúng. Tôi nghĩ rằng bạn đã không hiểu những gì lệnh của tôi đang làm.
Benubird

@Benubird Tôi cũng không thể nhận lệnh của bạn cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dđể xuất bất cứ thứ gì. Lệnh của tôi nên đọc (ls 1; ls 2) | sort | uniq -d, không có -u, để hiển thị giao điểm danh sách. @ MikaëlMayer đã đúng khi lệnh ban đầu của tôi bị hỏng.
nitơ

@nitrogen Lý do tại sao tôi sử dụng mèo, là vì tôi muốn đây là một giải pháp tổng quát, để bạn có thể thay thế lsbằng một thứ khác, vd find. Giải pháp của bạn không cho phép điều này, bởi vì nếu một trong hai lệnh trả về hai dòng giống nhau, nó sẽ chọn nó thành một bản sao. Của tôi hoạt động ngay cả khi người dùng muốn làm ls 1/*và so sánh tất cả các tệp trên các thư mục con. Nếu không, vâng, nó hoạt động như là tốt. Nó có thể là của tôi là bash cụ thể.
Benubird

2

Tham gia là một tùy chọn tốt khác tùy thuộc vào đầu vào và đầu ra mong muốn

join -j1 -a1 <(ls 1) <(ls 2)

-1

Có một câu hỏi khác về Stackoverflow "Mảng giao nhau trong bash", được đánh dấu là một bản sao của câu hỏi này. Theo tôi, nó không hoàn toàn giống nhau, vì câu hỏi đó nói về việc so sánh hai mảng bash, trong khi câu hỏi này tập trung vào các tệp bash. Câu trả lời một dòng cho câu hỏi khác, hiện đang đóng, như sau:

# List1=( 0 1 2 3 4   6 7 8 9 10 11 12)
# List2=(   1 2 3   5 6   8 9    11 )
# List3=($(comm -12 <(echo ${List1[*]}| tr " " "\n"| sort) <(echo ${List2[*]} | tr " " "\n"| sort)| sort -g))
# echo ${List3[*]}
1 2 3 6 8 9 11

Tiện ích comm thực hiện sắp xếp chữ và số, trong khi câu trả lời "Mảng giao nhau trong bash" sử dụng số; do đó việc sử dụng "sort" và "sort -g".

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.