Trong shell, làm thế nào tôi có thể tail
tạ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ể tail
tạ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 .bashrc
và 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 tail
chí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 find
hỗ trợ -print0
như 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 -printf
cho 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 find
hỗ 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ó -printf
bạ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ì stat
nó 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 1
ngăn chặn việc find
liệt kê nội dung của thư mục con và \! -type d
bỏ 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, sort
bạ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 -z
tắc.; cho tiêu chuẩn sort
khô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 sort
hai đ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; -znr
chỉ 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 sort
ký tự phân cách trường và -k 1,2
chỉ đị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à sort
sẽ xem xét đầu tiên 1000000000
và sau đó 0000000000
khi đặt hàng hồ sơ này. Các -n
tùy chọn cho sort
sử 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
là -r
cho "đả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, -r
thay đổ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 head
và tail
khô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 read
hai đ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 while
lặp lại, nó có dấu thời gian và một tên tệp duy nhất. Lưu ý rằng đó -d
là một phần mở rộng GNU; nếu bạn chỉ có một tiêu chuẩn read
thì 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 record
biế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 cut
tiệ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 printf
và 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 cut
hai đ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 sort
dấu phân cách .
được sử dụng). Thứ hai cut
được hướng dẫn với -f
chỉ 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à cut
sẽ đọ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: break
thoá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 tail
trê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 tail
là 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ẫntail
rằ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)
head
và tail
khô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ý đó.
-1
là không cần thiết, ls
điều đó cho bạn khi nó ở trong một đường ống. So sánh ls
và 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
, mtime
và ctime
, nhưng trái với Microsoft Windows, ctime
khô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 ls
lệ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 m
biểu thị thời gian sửa đổi m[Mwhms][-|+]n
và trước đó o
có nghĩa là nó được sắp xếp theo một cách ( O
sắ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`
ls
khô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)"