ディレクトリ内の最新のファイルを「テール」にする方法


20

シェルでtailは、ディレクトリに作成された最新のファイルをどのように作成できますか?


1
近づいて来て、プログラマは尾を引く必要があります!
アミット

クローズは、スーパーユーザーまたはサーバー障害に移行する場合のみです。質問はそこに住んでいて、興味があるかもしれないより多くの人々がそれを見つけるでしょう。
-Mnementh

ここでの本当の問題は、ディレクトリ内の最新の更新ファイルを見つけることであり、すでに回答されていると思います(ここでも、スーパーユーザーでも、思い出せません)。
dmckee

回答:


24

lsの出力を解析しないでください!lsの出力の解析は難しく、信頼性がありません

これを行う必要がある場合は、findを使用することをお勧めします。もともと私はあなたにソリューションの要点を伝えるために単純な例を持っていましたが、この答えはある程度一般的であるため、これを修正してすべての入力でコピー/貼り付けして使用できる安全なバージョンを提供することにしました。快適に座っていますか?現在のディレクトリにある最新のファイルを提供するonelinerから始めます。

tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"

ワンライナーではありませんか?ここでもシェル関数として、読みやすいようにフォーマットされています。

latest-file-in-directory () {
    find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
            sort -znr -t. -k1,2 | \
            while IFS= read -r -d '' -r record ; do
                    printf '%s' "$record" | cut -d. -f3-
                    break
            done
}

そして今、その onelinerとして:

tail -- "$(latest-file-in-directory)"

他のすべてが失敗した場合、上記の関数をに含めて、.bashrc1つの警告で解決された問題を検討できます。仕事をやりたいだけなら、これ以上読む必要はありません。

これに関する注意点は、1つ以上の改行で終わるファイル名がまだtail正しく渡されないことです。この問題を回避することは複雑であり、このような悪意のあるファイル名に遭遇した場合、より危険なものではなく、「No such file」エラーに遭遇する比較的安全な動作が発生すれば十分と考えます。

ジューシーな詳細

好奇心が強い人にとっては、これがどのように機能するのか、なぜ安全なのか、他の方法がおそらくそうでないのかについての退屈な説明です。

危険、ウィル・ロビンソン

まず第一に、ファイルパスを区切るのに安全な唯一のバイトは、Unixシステムのファイルパスで一般的に禁止されている唯一のバイトであるため、nullです。ファイルパスのリストを処理するときは、区切り文字としてnullのみを使用し、あるプログラムから別のプログラムに単一のファイルパスを渡すときは、任意のバイトでチョークしない方法で行うことが重要です。ファイル名に新しい行やスペースが含まれないと仮定することで(偶然でも)失敗する、この問題やその他の問題を解決する多くの正しい方法があります。どちらの仮定も安全ではありません。

今日の目的のためのステップ1は、ヌル区切りのファイルのリストを検索から取得することです。GNUなどのfindサポートがある場合、これは非常に簡単です-print0

find . -print0

しかし、このリストはまだどれが最新のものかを教えていないので、その情報を含める必要があります。findの-printfスイッチを使用して、出力に表示するデータを指定できます。findサポートのすべてのバージョン-printf(標準ではありません)ではありませんが、GNU findはサポートしています。自分がいない-printfことに気付いた場合、標準ではないので-exec stat {} \;、どの時点で移植性のすべての希望をあきらめる必要statがあります。とりあえず、GNUツールを持っていると仮定して話を進めます。

find . -printf '%T@.%p\0'

ここで%T@は、Unixエポックの始まりからピリオドが続き、その後に秒の小数部を示す数字が続く、秒単位の変更時間であるprintf形式を要求しています。これに別のピリオドを追加してから%p(ファイルへのフルパス)、ヌルバイトで終了します。

今私が持っています

find . -maxdepth 1 \! -type d -printf '%T@.%p\0'

言うまでもありませんが、完全であるために、サブディレクトリの内容を一覧表示する-maxdepth 1ことを防ぎfind\! -type dあなたがしたくないと思われるディレクトリをスキップしますtail。これまでのところ、現在のディレクトリに変更時刻情報を含むファイルがあるため、その変更時刻で並べ替える必要があります。

正しい順序で入手する

デフォルトでsortは、入力は改行で区切られたレコードであると想定されます。GNU sortをお持ちの場合は、-zスイッチを使用して代わりにヌル区切りのレコードを期待するように依頼できます。標準sortでは解決策はありません。私は最初の2つの数字(秒と秒の小数)でソートすることにのみ興味があり、実際のファイル名でソートしたくないので、sort2つのことを伝えます:最初に、ピリオド(.)をフィールド区切り文字と見なす必要があること次に、レコードの並べ替え方法を検討するときに、最初と2番目のフィールドのみを使用する必要があること。

| sort -znr -t. -k1,2

まず、価値のない3つの短いオプションをまとめています。-znr簡潔な言い方です-z -n -r)。その後-t .(スペースはオプション)sort、フィールド区切り文字に-k 1,2指示し、フィールド番号を指定します:最初と2番目(sortゼロではなく1からフィールドをカウントします)。現在のディレクトリのサンプルレコードは次のようになります。

1000000000.0000000000../some-file-name

この手段は、sort最初のを見ていきます1000000000と、その後0000000000、このレコードを注文するとき。この-nオプションはsort、これらの値を比較するときに数値比較を使用するように指示します。両方の値が数値であるためです。数字の長さが固定されているため、これは重要ではないかもしれませんが、害はありません。

与えられた他のスイッチsort-r「リバース」用です。デフォルトでは、数値ソートの出力は最小値が最初に-rなり、最小値が最後に、最大値が最初にリストされるように変更されます。これらの数値はタイムスタンプであるため、値が大きいほど新しいことになり、最新のレコードがリストの先頭に配置されます。

重要なことだけ

ファイルパスのリストが表示されるsortと、目的の答えが最上部に表示されます。残っているのは、他のレコードを破棄し、タイムスタンプを削除する方法を見つけることです。残念ながら、GNU headでさえtail、ヌル区切りの入力で動作させるためのスイッチを受け入れません。その代わりに、whileループを一種の貧乏人として使用しますhead

| while IFS= read -r -d '' record

まずIFS、ファイルのリストが単語分割の対象にならないように設定を解除します。次に、read2つのことを説明します。入力内のエスケープシーケンスを解釈しないでください-r-d)。入力はヌルバイト()で区切られています。ここでは、空の文字列''を使用して、「区切り文字なし」別名nullで区切られています。各レコードは変数に読み込まれるrecordため、whileループが繰り返されるたびに、単一のタイムスタンプと単一のファイル名が使用されます。これ-dはGNU拡張機能であることに注意してください。標準がある場合、readこの手法は機能せず、頼りになることはほとんどありません。

record変数には3つの部分があり、すべてピリオド文字で区切られていることがわかっています。cutユーティリティを使用して、それらの一部を抽出することができます。

printf '%s' "$record" | cut -d. -f3-

ここでは、レコード全体printfがそこにパイプされcut、そこからパイプされます。bashでは、パフォーマンスを向上させるためにhere文字列を使用してこれをさらに簡略化できますcut -d. -3f- <<<"$record"cut2つのことを説明します。まず、-dフィールドを識別するための特定の区切り文字を使用する必要があります(sort区切り文字.が使用される場合と同様)。2番目cut-f、特定のフィールドの値のみを印刷するように指示されています。フィールドリストは3-、3番目のフィールドと後続のすべてのフィールドの値を示す範囲として指定されます。つまりcut、2番目までのすべてを読み取り、無視します。.、レコード内で見つかっ、残り(ファイルパス部分)を出力ます。

最新のファイルパスを出力すると、続行する必要はありませんbreak。2番目のファイルパスに移動させずにループを終了します。

残っているのはtail、このパイプラインによって返されたファイルパスで実行されていることだけです。私の例では、パイプラインをサブシェルで囲むことでこれを行ったことに気付いたかもしれません。気付いていないかもしれませんが、サブシェルを二重引用符で囲んでいます。これは重要なことです。最後に、すべてのファイル名に対して安全であるためのこの努力のすべてでさえ、引用されていないサブシェル拡張はまだ物事を壊す可能性があるからです。より詳細な説明は、あなたが興味を持っている場合は可能です。呼び出しの2番目に重要だが見過ごされやすい側面tail--、ファイル名を展開する前にオプションを提供したことです。これは指示しますtailこれ以上オプションが指定されておらず、それに続くすべてがファイル名であるため、で始まるファイル名を安全に処理できます-


1
@AakashM:たとえば、ファイルの名前に「異常な」文字が含まれている場合(ほとんどすべての文字が有効)、「驚くべき」結果が得られる可能性があるためです。
ジョンツウィンク

6
ファイル名に特殊文字を使用する人は、取得するすべてのものに値します:

6
paxdiabloを見ると、その発言は非常に苦痛でしたが、2人が投票しました!バグのあるソフトウェアを書く人は、意図的にすべてを手に入れるに値する。
ジョンツウィンク

4
多分それはまだだろう、ヘルプ誰か...上記の溶液が原因検索中-printfオプションの欠如が、原因のstatコマンドの違いだけOSX上の次の作品にOSXで仕事をしないようにtail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
audio.zoom

2
「残念ながらGNU headでさえtail、ヌル区切りの入力で動作させるスイッチを受け入れません。」私の代わりhead… | grep -zm <number> ""
カミルマシオロウスキ

22
tail `ls -t | head -1`

スペースを含むファイル名が心配な場合は、

tail "`ls -t | head -1`"

1
しかし、最新のファイルにスペースや特殊文字が含まれているとどうなりますか?この問題を回避するには、 ``の代わりに$()を使用し、サブシェルを引用してください。
phogg

私はこれが好き。クリーンでシンプル。あるべきです。

6
堅牢で正しいものを犠牲にするなら、簡単で簡単になります。
phogg

2
まあ、それは本当にあなたが何をしているのかに依存します。すべての可能なファイル名で常に動作するソリューションは非常に優れていますが、制約された状況(たとえば、既知の奇妙ではない名前のログファイル)では不要な場合があります。

これはこれまでで最もクリーンなソリューションです。ありがとうございました!
デミス

4

次を使用できます。

tail $(ls -1t | head -1)

$()構築物は、コマンド実行し、サブシェルを開始しls -1tていることや配管(時間順、1行に1つずつのすべてのファイルを一覧表示)head -1最初の行(ファイル)を取得するために。

次に、そのコマンドの出力(最新のファイル)が渡されtailて処理されます。

作成された最新のディレクトリエントリである場合、これによりディレクトリを取得するリスクが生じることに注意してください。エイリアスでそのトリックを使用して、これらのログファイルのみを含むディレクトリ内の最新のログファイル(ローテーションセットから)を編集しました。


これ-1は必要でlsはありません。パイプの中にあるときにそれを行います。比較lsしてls|cat、例えば。
追って通知があるまで一時停止します。

Linuxの場合がそうかもしれません。「真の」Unixでは、プロセスは出力がどこに向かっているかに基づいて動作を変更しませんでした。これにより、パイプラインデバッグが本当に面倒になります:-)

うーん、それが正しいかどうかはわかりません-ISTRは、フィルターを介して出力をパイプするときに4.2BSDで列形式の出力を取得するために "ls -C"を発行する必要があります。とにかく「真のUnix」とは何ですか?

引用!引用!ファイル名にはスペースが含まれています!
ノーマンラムジー

@TMN:1つの真のUnixの方法は、非人間の消費者のためにlsに依存しないことです。「出力が端末への場合、形式は実装定義です。」-これは仕様です。確認したい場合は、ls -1またはls -Cと言う必要があります。
phogg

4

POSIXシステムでは、「最後に作成された」ディレクトリエントリを取得する方法はありません。各ディレクトリエントリにはatimemtimeおよびctimeがありますが、Microsoft Windowsとは異なり、これctimeはCreationTimeではなく「最終ステータス変更の時間」を意味します。

ですから、あなたが得ることができる最善の方法は、「最後に変更されたファイルの末尾」です。これは他の回答で説明されています。私はこのコマンドに行きます:

tail -f "$(ls -tr | sed 1q)"

lsコマンドを囲む引用符に注意してください。これにより、スニペットはほぼすべてのファイル名で機能します。


よくやった。まっすぐに。+1
ノーマンラムジー

4

watchを使用してファイルサイズの変更を確認したいだけです。

watch -d ls -l

3

zsh

tail *(.om[1])

参照:http : //zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers、ここでmは変更時刻を示しm[Mwhms][-|+]n、前述のo方法は、1つの方法でソートされることを意味します(O他の方法でソートする)。これ.は通常のファイルのみを意味します。括弧内[1]で最初のアイテムを選択します。3つの使用を選択し[1,3]、最も古い使用を取得します[-1]

短くて便利で、を使用しませんls


1

これを行う方法はおそらく100万通りありますが、私が行う方法は次のとおりです。

tail `ls -t | head -n 1`

バックティック間のビット(文字のような引用)が解釈され、結果が末尾に返されます。

ls -t #gets the list of files in time order
head -n 1 # returns the first line only

2
バックティックは悪です。代わりに$()を使用してください。
ウィリアムパーセル

1

シンプルな:

tail -f /path/to/directory/*

私にとってはうまくいきます。

問題は、tailコマンドを開始した後に生成されるファイルを取得することです。ただし、それが必要でない場合(上記のすべての解決策では問題にならないため)、アスタリスクは単純な解決策であるIMOです。



0

誰かがそれを投稿し、何らかの理由でそれを消去しましたが、これが唯一の機能するので、...

tail -f `ls -tr | tail`

ディレクトリを除外する必要がありますか?
-13:

1
最初にこれを投稿しましたが、Sorpigalからの出力を解析するlsことは最も賢明なことではないことに同意するため、削除しました...-
クリストフ

私はそれを素早くて汚い、それの中にディレクトリは必要ありません。あなたがあなたの答えを追加しますので、もし、私は1つことを受け入れます
イタイMoav -Malimovka

0
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`

説明:

  • ls -lt:変更時刻でソートされたすべてのファイルとディレクトリのリスト
  • grep -v ^ d:ディレクトリを除外します
  • head -2以降:必要なファイル名の解析

1
+1は賢い、-2はls出力を解析する、-1はサブシェルをクォートしない、-1は魔法の「フィールド8」の仮定(移植性がない!)、-1 が賢すぎることを示します。総合スコア:-4。
phogg

@ソルピガル合意。しかし、悪い例であることに満足しています。
アミット

はい、それは非常に多くの数の間違っただろうと想像しませんでした
アミット

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.