指定されたファイルの前にソートされたファイルを探す

指定されたファイルの前にソートされたファイルを探す

多くのファイルを含むディレクトリがあります。

例:

aaa.txt
bbb.txt
ccc.txt
ddd.txt

ランダムな文字列(必ずしもファイル名である必要はありません)が与えられたら、その文字列の前にソートされたすべてのファイルを見つけたい(通常のアルファベット順にソート)。

例:合計をccc.txt探したいです。bbb.txtaaa.txt

ファイル名には通常のASCII文字のみが含まれています。LC_ALL=Cそれは推測することができる。隠しファイルがありません(で始まる.)。

潜在的な解決策は次のとおりです(作成されたテストを含む)。

$ find -isnamelessthan ccc.txt
aaa.txt
bbb.txt

これはどのように達成できますか?

答え1

そしてzsh

print -rC1 -- **/*(NDe['[[ $REPLY:t < ccc.txt ]]'])

どこ:

  • print -rC1 -- printrsにawおよびon 1 Columnパラメータがあります。
  • **/このように、再帰検索のためにすべてのレベルのサブディレクトリ(0を含む)を一致させますfind
  • (...)一致をさらに制限するグローバル修飾子です。
    • Nprint:一致するものがない場合にエラーが報告されず、何も印刷されないようにnullglob:
    • D:dotglobはと同じで、find隠しファイルを除外しません。
    • e['code']:コードを実行してファイルを選択する必要があることを確認してください。ここのコードは、ail(考慮中のファイルパスのデフォルト名)の[[ $REPLY:t < ccc.txt ]]語彙比較(memcmp()代わりにロケール認識関数を使用)を実行しますstrcoll()t$REPLYccc.txt

GNUシステムでは、(すべてのシェルで)次のコマンドを使用して同様の操作を実行できます。

find . -mindepth 1 -printf '%P\0' | sort -z |
  LC_ALL=C gawk -v RS='\0' -F/ '$NF < "ccc.txt"'

どこ:

  • -mindepth 1、スタートアップファイル(.)を除外します。! -name .他のスタートアップファイル²には展開されませんが、標準を使用することもできます。
  • -printf '%P\0'dir/aaa.txtfor のように、開始ファイルへの相対的なファイルパスを./dir/a.txtNUL バイト (ファイルパスに現れない唯一のバイト値) で区切って印刷します。
  • sort -z/ globsstrcoll()に基づいてリストをソートします。ls
  • LC_ALL=Cstrcoll()sで使用されているように)を(ASCIIベースのシステムで)に変換します。awk<memcmp()
  • -v RS='\0'入力RレコードS区切り文字をNULバイトに設定します(ORS新しい行にはデフォルト値を残します)。
  • -F/、省略形はフィールド区切り記号-v FS=/をに設定します。FS/
  • $NF < "ccc.txt":最後のフィールドを"ccc.txt"語彙と比較し、trueの場合は、デフォルトジョブ({print}略語{print $0})を実行してレコードを印刷します。

述語の場合は、-isnamelessthan find次のことができます(zshから)。

alias -g -- -isnamelessthan='-exec zsh -c "[[ \$1:t < \$2 ]]" zsh {}'

次のように使用されます。

find . -isnamelessthan ccc.txt ';' -print

(各ファイルを確認するために1つのインスタンスを実行するので、効率的ではありませんzsh。)


¹ glob 自体はロケールに従ってソートされているので、次のようにします。strcoll()

find /path/to/dir ! -name dir²内部的に呼び出されるファイルを除外するため、これはできませんが、dir実行できますfind /path/to/dir/. ! -name .

答え2

ファイル名に改行文字が含まれていないとし、awk を使用します。

$ printf '%s\n' * | awk '$0 >= "ccc.txt"{exit} 1'
aaa.txt
bbb.txt

答え3

testシステムのユーティリティがある文字列が別の文字列の前にあるかどうかを判断する非標準演算子をサポートしている場合は、<次のコマンドで使用できますfind

find . -exec test {} '<' ./ccc.txt \; -print

または、

find . -exec [ {} '<' ./ccc.txt ] \; -print

ここでは、現在のディレクトリに基づいてファイルのパス名を使用して比較します。他のパス名にも同じことが当てはまるからです。この<演算子はとして引用されるか、'<'シェルがそれをリダイレクト演算子として解釈しないようにする必要があります"<"\<

テストが成功すると、述語は-printパス名を出力します。

検索を通常のファイルに制限し、サブディレクトリに移動せず、隠された名前(または考えられる他の基準)を避けるなど、追加のテストを追加します。

find . ! -path . -prune ! -name '.*' -type f -exec [ {} '<' ./ccc.txt ] \; -print

答え4

gawkStéphane ChazelasとEd Mortonは、awk問題を一行でエレガントに解決するような素晴らしい答えを使用して公開しました。

しかし、将来のプログラマーはawkこれらのソリューションを詳しく理解する方法を知る必要があります。だから私の場合は、単純なforループを使用する方が良いと思います。

最高の説明があるので、Stéphaneの答えを受け入れて、ここに私の解決策も残しておきます。

target="ccc.txt"
arr=()

target="ccc.txt"
arr=()

for f in * ; do
    # You can compare all string (also non-numeric) using <
    # -f handles the case when there are no files present
    if [[ -f $f && $f < $target ]] ; then arr+=("$f") ; fi
done

echo ${arr[@]}

関連情報