Listen Sie Zeichenfolgen auf, die Teilzeichenfolgen anderer Zeichenfolgen in der Liste sind

7

Ich habe eine Liste von Namen wie folgt:

dog_bone
dog_collar
dragon
cool_dragon
lion
lion_trainer
dog

Ich muss Namen extrahieren, die in anderen Namen wie folgt vorkommen:

dragon
lion
dog

Ich habe die uniqManpage durchgesehen , aber sie scheint ganze Zeilen zu vergleichen, keine Zeichenfolgen. Gibt es eine Möglichkeit, dies mit einer Bash-Funktion zu tun?

Fragenüberlauf
quelle
1
Wenn dog,, dog_boneund dog_bonesalle in der Datei erscheinen, was sollte ausgedruckt werden?
Mark Plotnick
@MarkPlotnick, dann beides dogund dog_bonewürde ausgedruckt werden.
Fragenüberlauf

Antworten:

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

Das führt einen read, zwei grepund manchmal einen printfBefehl pro Zeile der Datei aus, ist also nicht sehr effizient.

Sie können das Ganze in einem awkAufruf erledigen :

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"

Dies bedeutet jedoch, dass die gesamte Datei im Speicher gespeichert ist.

Stéphane Chazelas
quelle
Genau das, was ich brauche. Ausgezeichnetes Zeug :)
Frage Überlauf
@stephane Es wäre besser, wenn Sie den Befehl awk ein wenig erklären.
Avinash Raj
1
@AvinashRaj Wahrscheinlich nur was indexmacht? "index (in, find) Hiermit wird die Zeichenfolge nach dem ersten Vorkommen der Zeichenfolgensuche durchsucht und die Position in Zeichen zurückgegeben, an der dieses Vorkommen in der Zeichenfolge in beginnt."
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
Glenn Jackman
quelle
3

Hier ist ein Perl-Ansatz. Dies muss auch die Datei in den Speicher laden:

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
terdon
quelle
3

Ein hackiger Weg, um zu tun, was Sie wollen. Ich bin mir nicht sicher, ob alle Ihre Beispiele einen Unterstrich enthalten oder nicht, aber Sie könnten diesen abtasten und verwenden sort | uniq -d, um eine Liste von Teilzeichenfolgen zu erstellen, die in einer bestimmten Datei mehr als einmal vorhanden sind, wobei Sie die tatsächliche Datei selbst als Liste verwenden feste Saiten grepüber den -FSchalter.

Beispiel

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

Das Obige funktioniert wie folgt.

  1. <(grep -v _ file.txt)erzeugt eine Liste des Inhalts des file.txtWeglassens der Zeilen, die einen Unterstrich ( _) enthalten.

    $ grep -v _ file.txt 
    dragon
    lion
    dog
  2. grep -oFf <(..) file.txtverwendet die Ergebnisse von # 1 als Liste von Zeichenfolgen mit fester Länge, grepdie in der Datei gefunden werden file.txt.

    $ grep -oFf <(grep -v _ file.txt) file.txt
    dog
    dog
    dragon
    dragon
    lion
    lion
    dog
  3. Die Ergebnisse dieses Befehls werden dann über die Befehle sort& ausgeführt uniq -d, mit denen die Einträge, die mehr als einmal vorkommen, unter den Ergebnissen aufgelistet werden, grep -oFfdie erzeugt wurden.

HINWEIS: Wenn Sie verstehen möchten, warum Sie LC_ALL=Cbei der Ausführung von sortund uniqAnrufe die Verwendung von verwenden müssen, sehen Sie sich hier die gute Antwort von @ Stephane an: Was macht "LC_ALL = C"? .

slm
quelle
Das ist falsch, da es gleichbedeutend ist mit grep -v _ file.txt. Verwenden LC_ALL=C sort | LC_ALL=C uniq -dstatt sort -uwürde funktionieren
Stéphane Chazelas
@StephaneChazelas - danke für das Feedback. Können Sie erklären, was los ist? Ich verstehe nicht, was sich Ihr Vorschlag ändern wird.
slm
grep -of <(grep -v _ file.txt) file.txtgibt immer die Zeilen zurück, die keine Unterstriche enthalten, weil sie mit sich selbst übereinstimmen (Sie vermissen auch einige -F, aber das ist ein anderes Problem).
Stéphane Chazelas
@StephaneChazelas - OK, ich verstehe endlich, was LC_ALL=Cjetzt in all Ihren Beispielen passiert . Ich bin endlich über dein A zu diesem Q gestolpert, komisch, dass ich dieses bis heute noch nie gesehen habe. Vielen Dank!
slm
Ihre Antwort geht davon aus, dass man überlegen möchte, ob fooes innerhalb ist foo_bar, aber nicht, ob a_bes innerhalb ist a_b_c. Es wird auch nicht funktionieren, wenn es ein foound gibt foobar.
Stéphane Chazelas
3

Hier ist eine bashVersionslösung 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
cuonglm
quelle
Ich habe Ihre Lösung hier hinzugefügt. Bitte ändern Sie bei Bedarf. :)
Ramesh
@Ramesh: Nein, diese Frage ist bei deiner anders.
Cuonglm
Hoppla. Entschuldigung, ich habe die Frage anfangs falsch verstanden :)
Ramesh