Liệt kê ra các chuỗi là chuỗi con của các chuỗi khác trong danh sách


7

Tôi có một danh sách các tên như vậy:

dog_bone
dog_collar
dragon
cool_dragon
lion
lion_trainer
dog

Tôi cần trích xuất các tên xuất hiện trong các tên khác như vậy:

dragon
lion
dog

Tôi nhìn qua uniqtrang người đàn ông, nhưng dường như so sánh toàn bộ dòng, không phải chuỗi. Có cách nào để làm điều này với chức năng bash không?


1
Nếu dog, dog_bonedog_bonestất cả xuất hiện trong tệp, những gì cần được in ra?
Đánh dấu Plotnick

@MarkPlotnick, sau đó cả hai dogdog_bonesẽ được in ra.
Câu hỏi tràn

Câu trả lời:


5
file=/the/file.txt
while IFS= read -r string; do
  grep -Fe "$string" < "$file" | grep -qvxFe "$string" &&
    printf '%s\n' "$string"
done < "$file"

Điều đó chạy một read, hai grepvà đôi khi một printflệnh trên mỗi dòng của tệp, vì vậy sẽ không hiệu quả lắm.

Bạn có thể làm toàn bộ trong một lần awkgọi:

awk '{l[NR]=$0}
     END {
       for (i=1; i<=NR; i++)
         for (j=1; j<=NR; j++)
           if (j!=i && index(l[j], l[i])) {
             print l[i]
             break
           }
     }' < "$file"

mặc dù điều đó có nghĩa là toàn bộ tập tin được lưu trữ trong bộ nhớ.


Chính xác những gì tôi cần. Công cụ tuyệt vời :)
Câu hỏi tràn vào

@stephane Sẽ tốt hơn nếu bạn giải thích lệnh awk một chút.
Avinash Raj

1
@AvinashRaj Có lẽ chỉ có những gì index? "index (in, find) Điều này tìm kiếm chuỗi trong lần xuất hiện đầu tiên của chuỗi tìm và trả về vị trí trong các ký tự nơi sự xuất hiện đó bắt đầu trong chuỗi."
Bernhard

5

bash

names=(
  dog_bone
  dog_collar
  dragon
  cool_dragon
  lion
  lion_trainer
  dog
)

declare -A contained                 # an associative array
for (( i=0; i < ${#names[@]}; i++ )); do 
    for (( j=0; j < ${#names[@]}; j++ )); do 
        if (( i != j )) && [[ ${names[i]} == *"${names[j]}"* ]]; then
            contained["${names[j]}"]=1
        fi 
    done
done
printf "%s\n" "${!contained[@]}"    # print the array keys
dog
dragon
lion

3

Đây là một cách tiếp cận Perl. Điều này cũng cần tải tập tin vào bộ nhớ:

perl -le '@f=<>; foreach $l1 (@f){ 
                    chomp($l1); 
                    foreach $l2 (@f){ 
                        chomp($l2); 
                        next if $l1 eq $l2; 
                        $k{$l1}++ if $l2=~/$l1/;
                    }
                } print join "\n", keys %k' file

3

Một cách hacky để làm những gì bạn muốn. Tôi không chắc liệu tất cả các ví dụ của bạn có bao gồm dấu gạch dưới hay không nhưng bạn có thể khóa nó và sử dụng sort | uniq -dđể tạo danh sách các chuỗi con có mặt nhiều lần trong một tệp đã cho, sử dụng chính tệp đó làm danh sách chuỗi cố định để grep, thông qua các -Fchuyển đổi.

Thí dụ

$ grep -oFf <(grep -v _ file.txt) file.txt |
    LC_ALL=C sort | LC_ALL=C uniq -d    
dog
dragon
lion

Các công việc trên như sau.

  1. <(grep -v _ file.txt)sẽ tạo ra một danh sách các nội dung file.txtbỏ qua các dòng có dấu gạch dưới ( _).

    $ grep -v _ file.txt 
    dragon
    lion
    dog
  2. grep -oFf <(..) file.txtsẽ sử dụng kết quả của # 1 làm danh sách các chuỗi có độ dài cố định grepsẽ tìm thấy trong tệp file.txt.

    $ grep -oFf <(grep -v _ file.txt) file.txt
    dog
    dog
    dragon
    dragon
    lion
    lion
    dog
  3. Kết quả của lệnh này sau đó được chạy qua các lệnh sort& uniq -dsẽ liệt kê các mục xảy ra nhiều lần trong số các kết quả grep -oFfđã tạo.

LƯU Ý: Nếu bạn muốn hiểu lý do tại sao bạn cần tranh thủ sử dụng LC_ALL=Ckhi thực hiện sortuniqgọi thì hãy xem câu trả lời hay của @ Stephane cho vấn đề này tại đây: "LC_ALL = C" làm gì? .


Điều đó sai vì nó tương đương với grep -v _ file.txt. Sử dụng LC_ALL=C sort | LC_ALL=C uniq -dthay vì sort -usẽ hoạt động
Stéphane Chazelas

@StephaneChazelas - cảm ơn bạn đã phản hồi. Bạn có thể giải thích những gì sai? Tôi không hiểu những gì bạn đề nghị sẽ thay đổi.
slm

grep -of <(grep -v _ file.txt) file.txtsẽ luôn trả về các dòng không chứa dấu gạch dưới vì chúng khớp với nhau (bạn cũng thiếu một số -F, nhưng đó là một vấn đề khác).
Stéphane Chazelas

@StephaneChazelas - OK Cuối cùng tôi cũng hiểu những gì LC_ALL=Cđang làm trong tất cả các ví dụ của bạn bây giờ. Cuối cùng tôi tình cờ gặp A của bạn đến Q đó, thật buồn cười tôi chưa bao giờ thấy cái đó cho đến ngày hôm nay. Cảm ơn!
slm

Câu trả lời của bạn cho rằng người ta muốn xem xét liệu foocó ở bên trong không foo_bar, nhưng không phải a_blà bên trong a_b_c. Nó cũng sẽ không hoạt động nếu có foo, và foobar.
Stéphane Chazelas

3

Đây là một giải pháp bashphiên bản 4.x:

#!/bin/bash

declare -A output
readarray input < '/path/to/file'

for i in "${input[@]}"; do
  for j in "${input[@]}"; do
    [[ $j = "$i" ]] && continue
    if [ -z "${i##*"$j"*}" ]; then
      if [[ ! ${output[$j]} ]]; then
        printf "%s\n" "$j"
        output[$j]=1
      fi
    fi
  done
done

Tôi đã thêm giải pháp của bạn ở đây. Hãy thay đổi nếu cần. :)
Ramesh

@Ramesh: Không, câu hỏi này khác với bạn.
cuonglm

Giáo sư. Xin lỗi, tôi đã hiểu nhầm câu hỏi ban đầu :)
Ramesh
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.