How can I watch the 17th (or last, if less) line in files of a folder?
With GNU awk
:
watch -x gawk '
FNR == 17 {nextfile}
ENDFILE {if (FNR) printf "%15s[%02d] %s\n", FILENAME, FNR, $0}' ./*
Which gives an output like:
./file1[17] line17
./short-file2[05] line 5 is the last
Note that the ./*
glob is expanded only once at the time watch
is invoked.
Your watch head -n 17 *
was an arbitrary command injection vulnerability as the expansion of that *
was actually interpreted as shell code by the shell that watch
invokes to interpret the concatenation of its arguments with spaces.
If there was a file called $(reboot)
in the current directory, it would reboot.
With -x
, we're telling watch
to skip the shell and execute the command directly. Alternatively, you could do:
watch 'exec gawk '\''
FNR == 17 {nextfile}
ENDFILE {if (FNR) printf "%15s[%02d] %s\n", FILENAME, FNR, $0}'\'' ./*'
For watch
to run a shell which would expand that ./*
glob at each iteration. watch foo bar
is in effect the same as watch -x sh -c 'foo bar'
. When using watch -x
, you can specify which shell you want and for instance pick a more powerful one like zsh
that can do recursive globbing and restrict to regular files:
watch -x zsh -c 'awk '\''...'\'' ./**/*(.)'
Without gawk
, you could still do something like:
watch '
for file in ./*; do
[ -s "$file" ] || continue
printf "%s: " "$file"
head -n 17 < "$file" | tail -n 1
done'
Giving an ouput like:
./file1: line17
./short-file2: line 5 is the last
But that would be a lot less efficient as it implies running several commands per file.
Combine head
and tail
like in these two examples:
$ seq 1 80 | head -n 17 | tail -n 1
17
$ seq 1 10 | head -n 17 | tail -n 1
10
So to solve your actual problem, command is:
watch 'for f in *; do head -n 17 -- "$f" 2>/dev/null | tail -n 1 ; done'
Note about the 2>/dev/null
part, it is needed because * will match directories and any files you might not have permission to read, which will produce error message, which you probably want to hide.