私のタイトル表現は少し奇妙かもしれないので、私の状況は次のようになります。
/a/b
/a/b/c
/a/b/c/d
/a/e/f/g/h
/a/e/f/g/h/i/j/k/l
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
リストにすでに存在する項目のサブパスであるすべての行をフィルタリングしたいと思います。
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
ディレクトリパスはから取得されるfind
ため、トップダウン順序で確実にソートする必要があります。配列または複数行の文字列に解析するソリューションはすべて歓迎します。
ベストアンサー1
私は、パス名リストがソートされない可能性があり、結果のパス名リストが入力と同じ順序でなければならないと仮定します。また、パス名に改行文字が含まれていないと仮定します。
使用/bin/sh
:
#!/bin/sh
set --
while IFS= read -r pathname; do
for p do
case $pathname in ("$p"/*) continue 2 ;; esac
done
set -- "$@" "$pathname"
done <list
printf '%s\n' "$@"
その後、ファイルからlist
一度に1行ずつパス名を読み込みます。許可されたパス名(最初は空のリスト)は、内部ループから一度に1つずつ読み取られた各パス名に対してテストされます。許可されたパス名が現在のパス名のディレクトリパスプレフィックスの場合、現在のパス名は削除されます(内部ループは外部ループの次の反復としてジャンプを使用しますcontinue 2
)。現在のパス名であるパス名を受け入れるディレクトリパスプレフィックスが見つからない場合は、現在のパス名が許可されます。
許可されているパス名のリストは場所パラメータに保持されます。
シェルはbash
明らかに上記のスクリプトを実行できますが、そのシェル用に特別に書かれたものが必要な場合は、次のように言うことができます。
#!/bin/bash
accepted=()
while IFS= read -r pathname; do
for p in "${accepted[@]}"; do
[[ $pathname == "$p"/* ]] && continue 2
done
accepted+=("$pathname")
done <list
printf '%s\n' "${accepted[@]}"
awk
上記と同じ方法を使用してください。
$ awk '{ for (i=1; i<=n; ++i) if (index($0, accepted[i] "/") == 1) next; accepted[++n]=$0 } END { for (i=1; i<=n; ++i) print accepted[i] }' list
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
コードがawk
改善されました。
{
for (i = 1; i <= n; ++i)
if (index($0, accepted[i] "/") == 1)
next
accepted[++n] = $0
}
END {
for (i = 1; i <= n; ++i)
print accepted[i]
}
awk
このプログラムとシェルコードバリアントの間の明らかな類似点を最初からすぐに見ることができるはずです。
index()
これは、許可されたパス名が現在のパス名のプレフィックスであるかどうかをテストするために使用されます。これを使用することもできますが、if ($0 ~ "^" acceped[i] "/")
この方法の欠点は、パス名自体が正規表現の一部として使用されることです。パス名になどの文字が含まれる場合、これは.
重要になります*
。