2

I have a bunch of log files in a folder. When I cd into the folder and look at the files it looks something like this.

$ ls -lhat
-rw-r--r--   1 root root 5.3K Sep 10 12:22 some_log_c48b72e8.log
-rw-r--r--   1 root root 5.1M Sep 10 02:51 some_log_cebb6a28.log
-rw-r--r--   1 root root 1.1K Aug 25 14:21 some_log_edc96130.log
-rw-r--r--   1 root root 406K Aug 25 14:18 some_log_595c9c50.log
-rw-r--r--   1 root root  65K Aug 24 16:00 some_log_36d179b3.log
-rw-r--r--   1 root root  87K Aug 24 13:48 some_log_b29eb255.log
-rw-r--r--   1 root root  13M Aug 22 11:55 some_log_eae54d84.log
-rw-r--r--   1 root root 1.8M Aug 12 12:21 some_log_1aef4137.log

I want to look at the most recent messages in the most recent log file. I can now manually copy the name of the most recent log and then perform a tail on it and that will work.

$ tail -n 100 some_log_c48b72e8.log 

This does involve manual labor so instead I would like to use bash-fu to do this.

I currently found this way to do it;

filename="$(ls -lat | sed -n 2p |  tail -c 30)"; tail -n 100 $filename

It works, but I am bummed out that I need to save data into a variable to do it. Is it possible to do this in bash without saving intermediate results into a variable?

cantdutchthis
  • 31,949
  • 17
  • 74
  • 114
  • The best solution is to have your logger use timestamp-based names for the log files, and use a fixed name like `current` for the most recent file. (Or a symlink with a fixed name to the most recent.) – chepner Sep 10 '15 at 14:06

4 Answers4

9
tail -n 100 "$(ls -at | head -n 1)"

You do not need ls to actually print timestamps, you just need to sort by them (ls -t). I added the -a option because it was in your original code, but note that this is not necessary unless your logfiles are "dot files", i.e. starting with a . (which they shouldn't).

Using ls this way saves you from parsing the output with sed and tail -c. (And you should not try to parse the output of ls.) Just pick the first file in the list (head -n 1), which is the newest. Putting it in quotation marks should save you from the more common "problems" like spaces in the filename. (If you have newlines or similar in your filenames, fix your filenames. :-D )

Instead of saving into a variable, you can use command substitution in-place.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 2
    In general, relying on the output of `ls` is not very much recommended, but in this case it seems to be the good way to go. You can also go for something like [`find . -type f -printf '%T@ %p\n' | sort -n | tail -1`](http://stackoverflow.com/a/4561987/1983854) – fedorqui Sep 10 '15 at 10:38
  • @fedorqui: You can rely on the output of `ls` any day. The discouraged thing is *parsing* the output of `ls -l`. Yes, `find` is the more powerful tool overall, and I can only recommend learning about it, but in this case plain `ls` gives acleaner solution. – DevSolar Sep 10 '15 at 10:39
  • 3
    [Why you shouldn't parse the output of ls(1)](http://mywiki.wooledge.org/ParsingLs) says _Unix allows almost any character in a filename, including whitespace, newlines, commas, pipe symbols, and pretty much anything else you'd ever try to use as a delimiter except NUL_ and then _ In its default mode, if standard output isn't a terminal, ls separates filenames with newlines_. So it is `ls` in general, not only `ls -l`. – fedorqui Sep 10 '15 at 10:42
  • 1
    @fedorqui: Ah... see, keeping your filesystem clean from "funny" names apparently gives one some blind spots over time. :-D – DevSolar Sep 10 '15 at 10:46
  • Haha, I have never created a file with a new line in its name, either : ) But it is always good to mention these kind of cases to prevent a weirdness feeling when the command stops working. – fedorqui Sep 10 '15 at 10:49
  • @fedorqui: Yes, it's definitely good to be aware of the potential issues! But in cases where you have full control over the exact format of the filenames in the directory (as is likely to be true here) it's much less of a concern :) – psmears Sep 10 '15 at 13:31
3

A truly ls-free solution:

tail -n 100 < <(
  for f in *; do
    [[ $f -nt $newest ]] && newest=$f
  done
  cat "$newest"
  )

There's no need to initialize newest, since any file will be newer than the null file named by the empty string.

It's a bit verbose, but it's guaranteed to work with any legal file name. Save it to a shell function for easier use:

tail_latest () {
  dir=${1:-.}
  size=${2:-100}
  for f in "$dir"/*; do
      [[ $f -nt $newest ]] && newest=$f
  done
  tail -f "$size" "$newest"
}

Some examples:

# Default of 100 lines from newest file in the current directory
tail_latest
# 200 lines from the newest file in another directory
tail_latest /some/log/dir 200

A plug for zsh: glob qualifiers let you sort the results of a glob directly, making it much easier to get the newest file.

tail -n 100 *(om[1,1])

om sorts the results by modification time (newest first). [1,1] limits the range of files matched to the first. (I think Y1 should do the same, but it kept giving me an "unknown file attribute" error.)

chepner
  • 497,756
  • 71
  • 530
  • 681
1

You can try this way also

ls -1t  | head -n 1  | xargs tail -c 50

Explanation :

ls   -1rht     -- list the files based on modified time in reverse order.
tail -n 1      -- get the last one file 
tail -c 50     -- show the last 50 character from the file.
Kalanidhi
  • 4,902
  • 27
  • 42
  • 2
    `ls -1rht`... Drop the `-1` (`ls` only columnizes the output if printing to terminal, output to a pipe is one a line anyway). Drop the `-r` and use `head` instead of `tail`. Drop the `-h` because, since you're not *printing* timestamps (`-l`), you don't need to tell `ls` to print them human-readable. ;-) – DevSolar Sep 10 '15 at 10:54
1

Without parsing ls, you'd use stat

tail -n 100 "$(stat -c "%Y %n" * | sort -nk1,1 | tail -1 | cut -d" " -f 2-)"

Will break if your filenames contain newlines.


version 2: newlines are OK

tail -n 100 "$(
    stat --printf "%Y:%n\0" * | 
    sort -z -t: -k1,1nr | 
    { IFS=: read -d '' time filename; echo "$filename"; }
)"
glenn jackman
  • 238,783
  • 38
  • 220
  • 352