Làm thế nào để `tail` tập tin mới nhất trong một thư mục


20

Trong shell, làm thế nào tôi có thể tailtạo tập tin mới nhất trong một thư mục?


1
đến đóng cửa, lập trình viên cần đuôi!
amit

Việc đóng chỉ dành cho việc di chuyển đến superuser hoặc serverfault. Câu hỏi sẽ sống ở đó, và nhiều người có thể quan tâm sẽ tìm thấy nó.
Mnementh

Vấn đề thực sự ở đây là tìm tệp cập nhật gần đây nhất trong thư mục và tôi tin rằng điều đó đã được trả lời (ở đây hoặc trên Super User, tôi không thể nhớ lại).
dmckee

Câu trả lời:


24

Đừng không phân tích đầu ra của ls! Phân tích đầu ra của ls là khó khăn và không đáng tin cậy .

Nếu bạn phải làm điều này, tôi khuyên bạn nên sử dụng find. Ban đầu tôi có ở đây một ví dụ đơn giản chỉ để cung cấp cho bạn ý chính của giải pháp, nhưng vì câu trả lời này có vẻ hơi phổ biến nên tôi quyết định sửa lại để cung cấp một phiên bản an toàn để sao chép / dán và sử dụng với tất cả các đầu vào. Bạn đang ngồi thoải mái? Chúng tôi sẽ bắt đầu với một oneliner sẽ cung cấp cho bạn tệp mới nhất trong thư mục hiện tại:

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)"

Không phải là một oneliner bây giờ, phải không? Đây lại là một hàm shell và được định dạng để dễ đọc hơn:

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
}

Và bây giờ như một oneliner:

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

Nếu vẫn thất bại, bạn có thể đưa chức năng trên vào .bashrcvà xem xét vấn đề đã được giải quyết, với một cảnh báo. Nếu bạn chỉ muốn hoàn thành công việc, bạn không cần phải đọc thêm.

Thông báo trước cho điều này là tên tệp kết thúc bằng một hoặc nhiều dòng mới sẽ vẫn không được chuyển thành tailchính xác. Giải quyết vấn đề này rất phức tạp và tôi cho rằng đủ nếu một tên tệp độc hại như vậy gặp phải hành vi tương đối an toàn khi gặp phải lỗi "Không có tệp như vậy" sẽ xảy ra thay vì bất kỳ điều gì nguy hiểm hơn.

Chi tiết ngon ngọt

Đối với người tò mò, đây là lời giải thích tẻ nhạt về cách thức hoạt động của nó, tại sao nó an toàn và tại sao các phương pháp khác có thể không.

Nguy hiểm, Will Robinson

Trước hết, byte duy nhất an toàn để phân định đường dẫn tệp là null vì đây là byte duy nhất bị cấm phổ biến trong đường dẫn tệp trên các hệ thống Unix. Điều quan trọng là khi xử lý bất kỳ danh sách các đường dẫn tệp nào chỉ sử dụng null làm dấu phân cách và khi bàn giao ngay cả một đường dẫn tệp duy nhất từ ​​chương trình này sang chương trình khác, để thực hiện theo cách không bị nghẹt các byte tùy ý. Có nhiều cách có vẻ đúng để giải quyết vấn đề này và các vấn đề khác không thành công bằng cách giả sử (thậm chí vô tình) rằng tên tệp sẽ không có dòng hoặc khoảng trắng mới trong đó. Không giả định nào là an toàn.

Đối với mục đích ngày nay, bước một là lấy danh sách các tệp được phân tách bằng null. Điều này khá dễ dàng nếu bạn có một findhỗ trợ -print0như GNU:

find . -print0

Nhưng danh sách này vẫn không cho chúng ta biết cái nào mới nhất, vì vậy chúng ta cần đưa thông tin đó vào. Tôi chọn sử dụng công cụ tìm kiếm -printfcho phép tôi chỉ định dữ liệu nào xuất hiện trong đầu ra. Không phải tất cả các phiên bản findhỗ trợ -printf(không phải là tiêu chuẩn) nhưng GNU find thì có. Nếu bạn thấy mình không có -printfbạn sẽ cần phải dựa vào -exec stat {} \;thời điểm nào bạn phải từ bỏ mọi hy vọng về tính di động vì statnó cũng không phải là tiêu chuẩn. Bây giờ tôi sẽ chuyển sang giả sử bạn có các công cụ GNU.

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

Ở đây tôi đang yêu cầu định dạng printf %T@là thời gian sửa đổi tính bằng giây kể từ khi bắt đầu thời kỳ Unix theo sau là một khoảng thời gian và sau đó là một số chỉ ra các phân số của một giây. Tôi thêm vào khoảng thời gian này và sau đó %p(đó là đường dẫn đầy đủ đến tệp) trước khi kết thúc bằng một byte null.

Bây giờ tôi có

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

Nó có thể đi mà không nói nhưng vì lợi ích của việc hoàn thành -maxdepth 1ngăn chặn việc findliệt kê nội dung của thư mục con và \! -type dbỏ qua các thư mục mà bạn không muốn tail. Cho đến nay tôi có các tệp trong thư mục hiện tại với thông tin về thời gian sửa đổi, vì vậy bây giờ tôi cần sắp xếp theo thời gian sửa đổi đó.

Làm cho nó theo đúng thứ tự

Theo mặc định, sortđầu vào của nó là các bản ghi phân cách dòng mới. Nếu bạn có GNU, sortbạn có thể yêu cầu nó mong đợi các bản ghi được phân tách bằng null thay vào đó bằng cách sử dụng công -ztắc.; cho tiêu chuẩn sortkhông có giải pháp. Tôi chỉ quan tâm đến việc sắp xếp theo hai số đầu tiên (giây và phân số của một giây) và không muốn sắp xếp theo tên tệp thực tế vì vậy tôi nói với sorthai điều: Thứ nhất, nó nên xem dấu chấm ( .) là dấu phân cách trường và thứ hai là nó chỉ nên sử dụng các trường thứ nhất và thứ hai khi xem xét cách sắp xếp các bản ghi.

| sort -znr -t. -k1,2

Trước hết tôi đang gói ba tùy chọn ngắn không có giá trị với nhau; -znrchỉ là một cách nói ngắn gọn -z -n -r). Sau đó -t .(không gian là tùy chọn) cho biết sortký tự phân cách trường và -k 1,2chỉ định số trường: thứ nhất và thứ hai ( sortđếm các trường từ một, không phải không). Hãy nhớ rằng một bản ghi mẫu cho thư mục hiện tại sẽ trông như sau:

1000000000.0000000000../some-file-name

Điều này có nghĩa là sortsẽ xem xét đầu tiên 1000000000và sau đó 0000000000khi đặt hàng hồ sơ này. Các -ntùy chọn cho sortsử dụng so sánh số khi so sánh các giá trị, bởi vì cả hai giá trị là những con số. Điều này có thể không quan trọng vì các số có độ dài cố định nhưng nó không gây hại.

Các chuyển đổi khác được đưa ra sort-rcho "đảo ngược." Theo mặc định, đầu ra của một loại số sẽ là số thấp nhất trước tiên, -rthay đổi nó để nó liệt kê các số thấp nhất cuối cùng và số cao nhất trước. Vì những con số này là dấu thời gian cao hơn sẽ có nghĩa là mới hơn và điều này đặt bản ghi mới nhất vào đầu danh sách.

Chỉ là những bit quan trọng

Khi danh sách các đường dẫn tệp xuất hiện từ sortđó, giờ đây có câu trả lời mong muốn mà chúng tôi đang tìm kiếm ở trên cùng. Những gì còn lại là tìm cách loại bỏ các hồ sơ khác và loại bỏ dấu thời gian. Thật không may, ngay cả GNU headtailkhông chấp nhận các thiết bị chuyển mạch để làm cho chúng hoạt động trên đầu vào được phân tách bằng null. Thay vào đó tôi sử dụng một vòng lặp while như một kiểu người nghèo head.

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

Đầu tiên tôi bỏ đặt IFSđể danh sách các tệp không bị chia tách từ. Tiếp theo tôi nói readhai điều: Không diễn giải các chuỗi thoát trong đầu vào ( -r) và đầu vào được phân định bằng một byte null ( -d); ở đây, chuỗi rỗng ''được sử dụng để chỉ "không phân cách" hay còn gọi là null. Mỗi bản ghi sẽ được đọc vào biến recordđể mỗi lần whilelặp lại, nó có dấu thời gian và một tên tệp duy nhất. Lưu ý rằng đó -dlà một phần mở rộng GNU; nếu bạn chỉ có một tiêu chuẩn readthì kỹ thuật này sẽ không hoạt động và bạn có ít khả năng truy đòi.

Chúng ta biết rằng recordbiến có ba phần với nó, tất cả được phân định bởi các ký tự dấu chấm. Sử dụng cuttiện ích có thể trích xuất một phần của chúng.

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

Ở đây toàn bộ hồ sơ được chuyển đến printfvà từ đó được dẫn đến cut; trong bash bạn có thể đơn giản hóa điều này hơn nữa bằng cách sử dụng chuỗi ở đây để cut -d. -3f- <<<"$record"có hiệu suất tốt hơn. Chúng tôi nói với cuthai điều: Đầu tiên -d, đó là một dấu phân cách cụ thể để xác định các trường (như với sortdấu phân cách .được sử dụng). Thứ hai cutđược hướng dẫn với -fchỉ in các giá trị từ các trường cụ thể; danh sách trường được đưa ra dưới dạng phạm vi 3-cho biết giá trị từ trường thứ ba và từ tất cả các trường sau. Điều này có nghĩa là cutsẽ đọc và bỏ qua mọi thứ lên đến và bao gồm cả thứ hai .mà nó tìm thấy trong bản ghi và sau đó sẽ in phần còn lại, đó là phần đường dẫn tệp.

Đã in đường dẫn tệp mới nhất, không cần phải tiếp tục: breakthoát khỏi vòng lặp mà không để cho nó chuyển sang đường dẫn tệp thứ hai.

Điều duy nhất còn lại là chạy tailtrên đường dẫn tệp được trả về bởi đường ống này. Bạn có thể nhận thấy trong ví dụ của tôi rằng tôi đã làm điều này bằng cách đặt đường ống trong một khung con; Những gì bạn có thể không nhận thấy là tôi kèm theo phần con trong dấu ngoặc kép. Điều này rất quan trọng vì cuối cùng, ngay cả với tất cả nỗ lực này để đảm bảo an toàn cho bất kỳ tên tệp nào, một bản mở rộng mạng con không được trích dẫn vẫn có thể phá vỡ mọi thứ. Một lời giải thích chi tiết hơn có sẵn nếu bạn quan tâm. Khía cạnh quan trọng thứ hai nhưng dễ bị bỏ qua đối với việc gọi taillà tôi đã cung cấp tùy chọn --cho nó trước khi mở rộng tên tệp. Điều này sẽ hướng dẫntailrằng không còn tùy chọn nào được chỉ định và mọi thứ sau đây là tên tệp, giúp an toàn khi xử lý tên tệp bắt đầu bằng -.


1
@AakashM: bởi vì bạn có thể nhận được kết quả "đáng ngạc nhiên", ví dụ: nếu một tệp có các ký tự "bất thường" trong tên của nó (hầu hết tất cả các ký tự đều hợp pháp).
John Zwinck

6
Những người sử dụng các ký tự đặc biệt trong tên tệp của họ xứng đáng với mọi thứ họ nhận được :-)

6
Thấy paxdiablo đưa ra nhận xét đó đã đủ đau đớn, nhưng sau đó hai người đã bỏ phiếu! Những người viết phần mềm lỗi cố ý xứng đáng với mọi thứ họ nhận được.
John Zwinck

4
Vì vậy, giải pháp trên không hoạt động trên osx do thiếu tùy chọn -printf trong tìm kiếm, nhưng các giải pháp sau chỉ hoạt động trên osx do sự khác biệt trong lệnh stat ... có thể nó vẫn sẽ giúp được ai đótail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
audio.zoom

2
"Thật không may, ngay cả GNU headtailkhông chấp nhận các thiết bị chuyển mạch để làm cho chúng hoạt động trên đầu vào được phân tách bằng null." Thay thế của tôi cho head: … | grep -zm <number> "".
Kamil Maciorowski

22
tail `ls -t | head -1`

Nếu bạn lo lắng về tên tệp có dấu cách,

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

1
Nhưng điều gì xảy ra khi tập tin mới nhất của bạn có khoảng trắng hoặc ký tự đặc biệt? Sử dụng $ () thay vì `` và trích dẫn subshell của bạn để tránh vấn đề này.
phogg

Tôi thích điều này. Sạch sẽ và đơn giản. Như nó phải vậy.

6
Thật dễ dàng để được sạch sẽ và đơn giản nếu bạn hy sinh mạnh mẽ và chính xác.
phogg

2
Vâng, nó phụ thuộc vào những gì bạn đang làm, thực sự. Một giải pháp luôn hoạt động ở mọi nơi, đối với tất cả các tên tệp có thể, rất hay, nhưng trong một tình huống bị hạn chế (ví dụ: tệp nhật ký với các tên không lạ) có thể không cần thiết.

Đây là giải pháp sạch nhất cho đến nay. Cảm ơn bạn!
demisx

4

Bạn có thể dùng:

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

Cấu $()trúc bắt đầu một lớp vỏ con chạy lệnh ls -1t(liệt kê tất cả các tệp theo thứ tự thời gian, mỗi tệp trên một dòng) và chuyển qua đó head -1để có được dòng đầu tiên (tệp).

Đầu ra của lệnh đó (tệp gần đây nhất) sau đó được chuyển qua tailđể được xử lý.

Hãy nhớ rằng điều này có nguy cơ nhận được một thư mục nếu đó là mục nhập thư mục gần đây nhất được tạo. Tôi đã sử dụng thủ thuật đó trong bí danh để chỉnh sửa tệp nhật ký gần đây nhất (từ bộ xoay) trong thư mục chỉ chứa các tệp nhật ký đó.


Điều đó -1là không cần thiết, lsđiều đó cho bạn khi nó ở trong một đường ống. So sánh lsls|cat, ví dụ.
Tạm dừng cho đến khi có thông báo mới.

Đó có thể là trường hợp của Linux. Trong Unix "thật", các quy trình đã không thay đổi hành vi của họ dựa trên việc đầu ra của họ sẽ đi đâu. Điều đó sẽ làm cho việc gỡ lỗi đường ống thực sự gây phiền nhiễu :-)

Hmmm, không chắc là đúng - ISTR phải phát hành "ls -C" để có được đầu ra được định dạng cột theo 4.2BSD khi dẫn đầu ra qua bộ lọc và tôi khá chắc chắn rằng ls theo Solaris hoạt động theo cách tương tự. "Một, Unix thực sự" là gì?

Báo giá! Báo giá! Tên tập tin có không gian trong đó!
Norman Ramsey

@TMN: Một cách Unix thực sự là không dựa vào ls cho người tiêu dùng không phải là người. "Nếu đầu ra là một thiết bị đầu cuối, định dạng được xác định theo thực hiện." - đây là thông số kỹ thuật. Nếu bạn muốn chắc chắn bạn phải nói ls -1 hoặc ls -C.
phogg

4

Trên các hệ thống POSIX, không có cách nào để có được mục nhập thư mục "được tạo lần cuối". Mỗi mục nhập thư mục có atime, mtimectime, nhưng trái với Microsoft Windows, ctimekhông có nghĩa là CreationTime, mà là "Thời gian thay đổi trạng thái cuối cùng".

Vì vậy, cách tốt nhất bạn có thể nhận được là "theo dõi tệp sửa đổi gần đây nhất", được giải thích trong các câu trả lời khác. Tôi sẽ thực hiện lệnh này:

đuôi -f "$ (ls -tr | sed 1q)"

Lưu ý các trích dẫn xung quanh lslệnh. Điều này làm cho đoạn trích hoạt động với hầu hết các tên tệp.


Công việc tốt đẹp. Thẳng đến điểm. +1
Norman Ramsey

4

Tôi chỉ muốn xem thay đổi kích thước tập tin bạn có thể sử dụng xem.

watch -d ls -l

3

Trong zsh:

tail *(.om[1])

Xem: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifier , ở đây mbiểu thị thời gian sửa đổi m[Mwhms][-|+]nvà trước đó ocó nghĩa là nó được sắp xếp theo một cách ( Osắp xếp theo cách khác). Các .phương tiện chỉ có các tập tin thông thường. Trong dấu ngoặc [1]chọn mục đầu tiên. Để chọn ba sử dụng [1,3], để có được sử dụng lâu đời nhất [-1].

Nó ngắn đẹp và không sử dụng ls.


1

Có thể có một triệu cách để làm điều này, nhưng cách tôi sẽ làm là:

tail `ls -t | head -n 1`

Các bit giữa các backticks (trích dẫn như các ký tự) được diễn giải và kết quả được trả về đuôi.

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

2
Backticks là ác. Sử dụng $ () thay thế.
William Pursell

1

Một đơn giản:

tail -f /path/to/directory/*

làm việc tốt cho tôi

Vấn đề là để có được các tập tin được tạo ra sau khi bạn bắt đầu lệnh tail. Nhưng nếu bạn không cần điều đó (vì tất cả các giải pháp trên không quan tâm đến nó), dấu hoa thị chỉ là giải pháp đơn giản hơn, IMO.



0

Ai đó đã đăng nó, và sau đó xóa nó vì một số lý do, nhưng đây là người duy nhất hoạt động, vì vậy ...

tail -f `ls -tr | tail`

bạn đã loại trừ các thư mục, phải không?
amit

1
Tôi đăng này ban đầu nhưng tôi đã xóa nó kể từ khi tôi đồng ý với Sorpigal rằng phân tích đầu ra từ lskhông phải là điều thông minh nhất để làm ...
ChristopheD

Tôi cần nó nhanh chóng và bẩn thỉu, không có thư mục trong đó. Vì vậy, nếu bạn thêm câu trả lời của mình, tôi sẽ chấp nhận câu trả lời đó
Itay Moav -Malimovka

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

Giải trình:

  • ls -lt: Danh sách tất cả các tệp và thư mục được sắp xếp theo thời gian sửa đổi
  • grep -v ^ d: loại trừ các thư mục
  • head -2 trở đi: phân tích tên tệp cần thiết

1
+1 cho thông minh, -2 để phân tích cú pháp ls đầu ra, -1 cho việc không trích dẫn phần phụ, -1 cho giả định "trường 8" ma thuật (không thể di động!) Và cuối cùng là -1 vì quá thông minh . Điểm tổng thể: -4.
phogg

@Sorpigal Đồng ý. Hạnh phúc khi là tấm gương xấu.
amit

vâng không tưởng tượng nó sẽ là sai lầm về rất nhiều tội
amit

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.