リスト内の他の文字列の部分文字列である文字列をリストします

リスト内の他の文字列の部分文字列である文字列をリストします

次のような名前のリストがあります:

dog_bone
dog_collar
dragon
cool_dragon
lion
lion_trainer
dog

次のように、他の名前に表示される名前を抽出する必要があります。

dragon
lion
dog

man ページを確認しましたuniqが、文字列ではなく行全体を比較しているようです。bash 関数を使用してこれを行う方法はありますか?

答え1

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

これは、ファイルの行ごとに 1 つread、2 つgrep、場合によっては 1 つのprintfコマンドを実行するため、あまり効率的ではありません。

awk1 回の呼び出しですべてを実行できます。

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"

ただし、これはファイル全体がメモリに保存されることを意味します。

答え2

バッシュ

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

以下は Perl のアプローチです。これもファイルをメモリに読み込む必要があります。

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

答え4

bashバージョンのソリューションは次のとおりです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

関連情報