- Display the last lines of a file in Unix
- Related documents
- How do I get the last non-empty line of a file using tail in Bash?
- 9 Answers 9
- Bash get last line from a variable
- 3 Answers 3
- How to read first and last line from cat output?
- How can I read first n and last n lines from a file?
- 10 Answers 10
- Explanation
Display the last lines of a file in Unix
Use the Unix command tail to read from standard input or a file and send the result to standard output (that is, your terminal screen). The format for using the tail command is:
Everything in brackets is an optional argument. If you don’t specify a filename, tail uses standard input.
Tail begins at distance +number from the beginning or -number from the end of the input. The number is counted in units of lines, blocks, or characters, according to the appended options -l , -b , or -c . When you don’t specify a unit, tail operates based on lines.
Specifying -r causes tail to print lines from the end of the file in reverse order. The default for -r is to print the entire file this way. Specifying -f causes tail not to quit at the end of the file, but rather to reread the file repeatedly (useful for watching a «growing» file such as a log file).
For example, given a file containing the English alphabet with each letter on a separate line, the command:
You can use tail with pipes. For example, to see the sizes of the last few files in the current directory, you could enter at the Unix prompt:
To save this information in a file in the current directory named mylist , at the Unix prompt, enter:
For more detailed information on tail , consult the Unix manual page by entering at the prompt:
At Indiana University, for personal or departmental Linux or Unix systems support, see Get help for Linux or Unix at IU.
Related documents
This is document acrj in the Knowledge Base.
Last modified on 2023-07-14 09:11:02 .
How do I get the last non-empty line of a file using tail in Bash?
How do I get the last non-empty line using tail under Bash shell? For example, my_file.txt looks like this:
Obviously, if I do tail -n 1 my_file.txt I will get an empty line. In my case I want to get bonjour . How do I do that?
9 Answers 9
Use tac, so you dont have to read the whole file:
@gsamaras the touchbar is also lick-able 👅. And, by the way, this worked for me on CEntOS 7.5 (vagrant box bento/centos-7.5 ).
This solution has the advantage of using just one tool.
Downside: Needs to read the WHOLE file, and assign EACH LINE to awk variables -> This can be quite CPU and IO intensive.
Yes, the solution is simple, but far from being efficient. An efficient solution would open the file, seek to the end and scan backward.
How about using grep to filter out the blank lines first?
$ cat rjh 1 2 3 $ grep "." rjh | tail -1 3
In grep «.» , that period is regex, meaning «any character». In regex a blank line has a beginning (^), an end ($ or \n), but nothing in the middle, so grepping for the period only returns lines that contain characters. E.g., not blank.
Instead of tac you can use tail -r if available.
Which versions of tail(1) have the -r option? The one in GNU coreutils doesn’t have it, cf. debbugs.gnu.org/18808
The issue with this solution is that if the file you’re scanning is a shell script file then it will only filter the echo output rather than the actual file contents, try 1) creating a script file with a few simple echo commands; 2) pasting the full file path into the console and entering » | tail -1″ at the end; notice that it will only output your last echo command’s output rather than the entire echo command.
Bash get last line from a variable
If I have a variable with multiple lines (text) in it, how can I get the last line out of it? I already figured out how to get the first line:
STRING="This is a multiple line variable test" FIRST_LINE=($) echo "$FIRST_LINE" # output: "This is a"
Probably there should be an operator for the last line. Or at least I assume that because with @ the first line comes out.
3 Answers 3
An easy way to do this is to use tail :
Using bash string manipulations:
$> str="This is a multiple line variable test" $> echo "$" variable test
$ will remove the longest match till \n from start of the string thus leaving only the last line in input.
If you want an array with one element per line from STRING , use
Then, the first line would be $ , and the last line would be $ . In older versions of bash , negative indices aren’t allowed and you’ll have to compute the last index manually: $-1]> .
IMO, 3.x should just be treated as a POSIX-compatible shell on par with dash , but with better features for interactive use. I consider the nearly 6-year-old bash 4.2 release a reasonable baseline assumption now.
Still using GNU bash, version 4.1.2 btw :), in which it is not supported. 🙁 Thought would be good point to add for a proper reference.
I also consider knowing what version of bash you are using to be part of your due diligence in asking a question. I am no longer presenting a laundry list of options to cater to 3 or more different versions of bash in every answer. I’m taking Python as my model, where the assumption is that you are using Python 3.3+ unless otherwise stated, and Python 2 pretty much means Python 2.7.
None taken! This is a little rant that’s been building in me for some time, and there’s no good place for it 🙂 I’d love to get some consensus and put a note in the bash tag info about what features we can reasonably assume, but no one asking questions reads that page anyway.
How to read first and last line from cat output?
When reading from stdin if would look like this (for example ps -ef ):
ps -ef | sed -e 1b -e '$!d' UID PID PPID C STIME TTY TIME CMD root 1931 1837 0 20:05 pts/0 00:00:00 sed -e 1b -e $!d
head & tail Solution:
When data is coming from a command ( ps -ef ):
ps -ef 2>&1 | (head -n1 && tail -n1) UID PID PPID C STIME TTY TIME CMD root 2068 1837 0 20:13 pts/0 00:00:00 -bash
awk Solution:
And also the piped example with ps -ef :
ps -ef | awk 'NR==1; END' UID PID PPID C STIME TTY TIME CMD root 1935 1837 0 20:07 pts/0 00:00:00 awk NR==1; END
Thank you! It’s the best answer because i cannot do (head -n1 file;tail -n1 file) i have very big command and pipe as last symbol. So | sed ‘1p;$!d’ shorter one.
Sorry for my English, if you don’t understand me — it’s my problem — tell me about it and i prepare better presentation for you.
@DavidConrad, to elaborate on what chaos said, -e 1b — on the first line, branch to the end of the sed script, at which point the implicit «print» happens. If the input is only one line long, sed ends. Otherwise, for all lines except the last, delete. On the last line, the implicit «print» happens again.
@mikeserv: 😉 check it when you get a chance. On a single line it efectively prevents doubling it on output, but on a multiple line work-case, it just preserves the last line. at least on GNU sed 4.2.2. Cheers.
sed -n ‘1p;$p’ file.txt will print 1st and last line of file.txt .
Note that if the input has only one line, it will be printed twice. You may prefer sed -e 1b -e ‘$!d’ if you don’t want that.
cb() < (($1-1>0)) && unset "ary[$1-1]"; > mapfile -t -C cb -c 1 ary < file
After this, you'll have an array ary with first field (i.e., with index 0 ) being the first line of file , and its last field being the last line of file . The callback cb (optional if you want to slurp all lines in the array) unsets all the intermediate lines so as to not clutter memory. As a free by-product, you'll also have the number of lines in the file (as the last index of the array+1).
$ mapfile -t -C cb -c 1 ary < <(printf '%s\n' ) $ declare -p ary declare -a ary='([0]="a" [25]="z")' $ # With only one line $ mapfile -t -C cb -c 1 ary < <(printf '%s\n' "only one line") $ declare -p ary declare -a ary='([0]="only one line")' $ # With an empty file $ mapfile -t -C cb -c 1 ary < <(:) declare -a ary='()'
How can I read first n and last n lines from a file?
How can I read the first n lines and the last n lines of a file? For n=2 , I read online that (head -n2 && tail -n2) would work, but it doesn't.
$ cat x 1 2 3 4 5 $ cat x | (head -n2 && tail -n2) 1 2
Also, the link you sent is not helpful because I do not know the range really. I am looking for a simple solution for this
Interestingly, cat x | (head -n2 && tail -n2) doesn't work but (head -n2 && tail -n2) < x does. I'll have to meditate a bit on why that is.
What would the expected output be if the input file was 3 lines long? Would it be 1 2 3 or 1 2 2 3 or something else? What if it was only 2 lines long - would the output be 1 2 1 2 or 1 1 2 2 or 1 2 or something else?
I don't think the head && tail trick is reliable. head from GNU coreutils behaves differently for pipes and regular files (source: the source), reading blockwise in one case but not the other. Depending on implementation details like that seems like a bad idea -- it's not guaranteed that head will leave everything it doesn't print for tail to work with.
10 Answers 10
head -n2 file && tail -n2 file
This isn't guaranteed to work even if your file is longer than 4 lines, if a single head buffer is so long enough that there aren't enough lines left in the file for tail to work.
Chances are you're going to want something like:
or if you need to specify a number and taking into account @Wintermute's astute observation that you don't need to buffer the whole file, something like this is what you really want:
I think the math is correct on that - hopefully you get the idea to use a rotating buffer indexed by the NR modded by the size of the buffer and adjusted to use indices in the range 1-n instead of 0-(n-1).
To help with comprehension of the modulus operator used in the indexing above, here is an example with intermediate print statements to show the logic as it executes:
$ cat tst.awk BEGIN < print "Populating array by index ((NR-1)%n)+1:" > < buf[((NR-1)%n)+1] = $0 printf "NR=%d, n=%d: ((NR-1 = %d) %%n = %d) +1 = %d ->buf[%d] = %s\n", NR, n, NR-1, (NR-1)%n, ((NR-1)%n)+1, ((NR-1)%n)+1, buf[((NR-1)%n)+1] > END < print "\nAccessing array by index ((NR+i-1)%n)+1:" for (i=1;i<=n;i++) < printf "NR=%d, i=%d, n=%d: (((NR+i = %d) - 1 = %d) %%n = %d) +1 = %d ->buf[%d] = %s\n", NR, i, n, NR+i, NR+i-1, (NR+i-1)%n, ((NR+i-1)%n)+1, ((NR+i-1)%n)+1, buf[((NR+i-1)%n)+1] > > $ $ awk -v n=3 -f tst.awk file Populating array by index ((NR-1)%n)+1: NR=1, n=3: ((NR-1 = 0) %n = 0) +1 = 1 -> buf[1] = 1 NR=2, n=3: ((NR-1 = 1) %n = 1) +1 = 2 -> buf[2] = 2 NR=3, n=3: ((NR-1 = 2) %n = 2) +1 = 3 -> buf[3] = 3 NR=4, n=3: ((NR-1 = 3) %n = 0) +1 = 1 -> buf[1] = 4 NR=5, n=3: ((NR-1 = 4) %n = 1) +1 = 2 -> buf[2] = 5 NR=6, n=3: ((NR-1 = 5) %n = 2) +1 = 3 -> buf[3] = 6 NR=7, n=3: ((NR-1 = 6) %n = 0) +1 = 1 -> buf[1] = 7 NR=8, n=3: ((NR-1 = 7) %n = 1) +1 = 2 -> buf[2] = 8 Accessing array by index ((NR+i-1)%n)+1: NR=8, i=1, n=3: (((NR+i = 9) - 1 = 8) %n = 2) +1 = 3 -> buf[3] = 6 NR=8, i=2, n=3: (((NR+i = 10) - 1 = 9) %n = 0) +1 = 1 -> buf[1] = 7 NR=8, i=3, n=3: (((NR+i = 11) - 1 = 10) %n = 1) +1 = 2 -> buf[2] = 8
+1 since this works in a pipe. You might add a more elaborated version which takes files (streams) into account having less then 4 (head+tail) lines..
@EdMorton But it would still need to buffer the whole stream in memory.. (However I don't see a way without buffering if it should work in a pipe, except saving the stream into a temporary file)
I understand but the bug was just that I was setting ORS='\n' when I should have been setting OFS='\n' . Now that that's fixed there's no need to explicitly hard-code "\n" s between fields.
This might work for you (GNU sed):
This keeps a window of 2 (replace the 2's for n) lines and then prints the first 2 lines and at end of file prints the window i.e. the last 2 lines.
Here's a GNU sed one-liner that prints the first 10 and last 10 lines:
If you want to print a '--' separator between them:
If you're on a Mac and don't have GNU sed, you can't condense as much:
Explanation
gsed -ne' invoke sed without automatic printing pattern space
-e'1,9' print the first 9 lines
-e'10' print line 10 with an appended '--' separator
-e':a;$p;N;21,$D;ba' print the last 10 lines
If you are using a shell that supports process substitution, another way to accomplish this is to write to multiple processes, one for head and one for tail . Suppose for this example your input comes from a pipe feeding you content of unknown length. You want to use just the first 5 lines and the last 10 lines and pass them on to another pipe:
cat | < tee >(head -5) >(tail -10) 1>/dev/null> | cat
The use of <> collects the output from inside the group (there will be two different programs writing to stdout inside the process shells). The 1>/dev/null is to get rid of the extra copy tee will try to write to it's own stdout.
That demonstrates the concept and all the moving parts, but it can be simplified a little in practice by using the STDOUT stream of tee instead of discarding it. Note the command grouping is still necessary here to pass the output on through the next pipe!
cat | < tee >(head -5) | tail -15 > | cat
Obviously replace cat in the pipeline with whatever you are actually doing. If your input can handle the same content to writing to multiple files you could eliminate the use of tee entirely as well as monkeying with STDOUT. Say you have a command that accepts multiple -o output file name flags: