4

Is there a linux command to return the last x % of a file? I know tail can return a number of lines (-n) or number of bytes (-c), but what if I wanted to get the last 25% of a file? Is there a command to do that?

2
  • 4
    No, but you can use wc to get a line/char count of the whole file, perform your own calculation, then pass the resulting value to tail – glenn jackman Jan 5 '18 at 19:20
  • 2
    If you just want to view the last x %, then you could use less with a per-cent offset e.g.less +p75 somefile – steeldriver Jan 5 '18 at 19:57
15

GNU split can do pretty much what you ask; given a text file in.txt, this will print the last quarter (part 4 out of 4) in terms of number of bytes (not lines), without splitting lines:

split -n l/4/4 in.txt

Here is the relevant documentation for split -n CHUNKS:

CHUNKS may be: [...] l/K/N output Kth of N to stdout without splitting lines

In the very specific case mentioned as an example in the question, 4/4 requests the fourth quarter, or the last 25% of the input file. For sizes that are not 1/n of the input, I do not think split provides such a straightforward solution.

0
6

Complex bash + stat + bc + tail solution for any percentage:

get_last_chunk () { 
    local p=$(bc <<<"scale=2; $1/100")
    tail -c $(printf "%.0f" $(echo "$(stat -c%s $2) * $p" | bc)) "$2"
}

  • $1 and $2 - are the function's 1st and 2nd arguments respectively
  • p - variable assigned with percentage value as float number (for ex. 0.14 or 0.55)
  • stat -c%s $2 - getting the actual size of the input file in bytes
  • tail -c N $2 - getting the last N bytes of the file

Or use the more simplified version:

get_last_chunk () { 
    tail -c "$(($(stat -c%s - < "$2") * $1 / 100))" < "$2"))"
}

Signature: get_last_chunk <percent> <filename>

Sample file.txt:

apples
oranges
bananas
cherries

Test cases:

get_last_chunk 17 file.txt
ries 

get_last_chunk 77 file.txt
oranges
bananas
cherries

get_last_chunk 29 file.txt
cherries
10
  • I like the creativity of this one. :) – raphael75 Jan 5 '18 at 19:52
  • You are probably better using stat -c%s filename.txt to get the filesize, rather than wc. stat will call fstat (or similar) to ask the fs driver for the filesize, rather than scanning the whole file. – CSM Jan 5 '18 at 23:07
  • 1
    echo {{expression}} | bc is a useless use of echo. We can instead bc <<< {{expression}}. – user137369 Jan 6 '18 at 2:15
  • @user137369, <<< means creating a temporary files filling it with the text, make that the stdin of the command and delete it. Whether it's better or worse than using the builtin echo with a pipe is up for debate. That also adds a requirement on the system having a zsh-like shell. But here, you don't use bc (nor printf) at all, you can use shell arithmetic expression. – Stéphane Chazelas Jan 6 '18 at 10:30
  • 1
    @user137369, here documents and here strings in bash are implemented with deleted temporary files like they were in the Bourne shell (which didn't have herestrings, just heredocs, herestrings come from zsh, so bash is a zsh-like shell in that regard) – Stéphane Chazelas Jan 6 '18 at 17:39
0

To get the last $1% in terms of number of lines, portably (POSIXly):

last_percent() (
  percent=${1?}; shift
  ret=0
  for file do
    lines=$(wc -l < "$file") &&
      tail -n "$((lines * percent / 100))" < "$file" || ret=$?
  done
  exit "$ret"
)

Example:

$ seq 12 > a; printf '%s\n' aaaaaa bbbbb cccc dd > b
$ last_percent 25 a b
10
11
12
dd

For the last $1% in terms of number of bytes, replace wc -l with wc -c and tail -n with tail -c. Beware though that the first output line would likely be partial. On the same files as above, that would give:

$ last_percent 25 a b
11
12
c
dd

With ksh93, you could write it with only builtins and not a single fork as:

last_percent() (
  percent=$1; shift
  ret=0
  for file do
    command /opt/ast/bin/cat < "$file" <#((EOF*(100-percent)/100)) || ret=$?
  done
  exit "$ret"
)

Using its <#((...)) seeking operator.

The same with zsh (except that cat is not builtin there):

zmodload zsh/system zsh/stat
last_percent() {
  local percent=$1 ret=0 file n
  shift
  for file do
    {
      sysseek -w end 0 &&
        sysseek -w end ' - systell(0) * percent / 100' &&
        cat
    } < $file || ret=$?
  done
  return $ret
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.