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?
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?
Câu trả lời:
Đừ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ờ mà 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.
Đố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.
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 đó.
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 sortlà -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.
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 headvà tailkhô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 -.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
headvà tailkhô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> "".
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`"
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ý đó.
-1là không cần thiết, lsđiều đó cho bạn khi nó ở trong một đường ống. So sánh lsvà ls|cat, ví dụ.
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, mtimevà ctime, 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.
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.
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
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.
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`
lskhông phải là điều thông minh nhất để làm ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Giải trình:
tail "$(ls -1tr|tail -1)"