95

I have text file. Task - get first and last line from file after

$ cat file | grep -E "1|2|3|4" | commandtoprint

$ cat file
1
2
3
4
5

Need this without cat output (only 1 and 5).

~$ cat file | tee >(head -n 1) >(wc -l)
1
2
3
4
5
5
1

Maybe awk and more shorter solution exist...

1
  • In your second example, wc -l has nothing to do with outputting the last line of a file. Jun 26, 2014 at 8:06

7 Answers 7

168

sed Solution:

sed -e 1b -e '$!d' file

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:

(head -n1 && tail -n1) <file

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:

awk 'NR==1; END{print}' file

And also the piped example with ps -ef:

ps -ef | awk 'NR==1; END{print}'
UID        PID  PPID  C STIME TTY          TIME CMD
root      1935  1837  0 20:07 pts/0    00:00:00 awk NR==1; END{print}
19
  • 3
    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.
    – dmgl
    Jun 25, 2014 at 11:09
  • 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.
    – dmgl
    Jun 25, 2014 at 11:12
  • 12
    Does head && tail really work for you? seq 10 | (head -n1 && tail -n1) prints 1 only. Jun 26, 2014 at 10:16
  • 2
    @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. Jun 26, 2014 at 19:10
  • 1
    @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.
    – Cbhihe
    Sep 19, 2016 at 23:15
34

sed -n '1p;$p' file.txt will print 1st and last line of file.txt .

1
  • 13
    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. Jun 25, 2014 at 11:06
11

A funny pure Bash≥4 way:

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).

Demo:

$ mapfile -t -C cb -c 1 ary < <(printf '%s\n' {a..z})
$ 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='()'
9

With sed you could delete lines if NOT the 1st one AND NOT the la$t one.
Use ! to NOT (negate) a condition and the X{Y..} construct to combine X AND Y conditions:

cmd | sed '1!{$!d;}'

or you could use a range - from 2nd to la$t - and delete all lines in that range except the la$t line:

cmd | sed '2,${$!d;}'
7

Without cat:

$ cat file |tee >(head -n1) >(tail -n1) >/dev/null
1
5

or

$ (head -n1 file;tail -n1 file)
1
5
1
  • 9
    In the first one, the order of the lines is not guaranteed. Jun 25, 2014 at 11:08
7

Using Perl:

$ seq 10 |  perl -ne 'print if 1..1 or eof'
1
10

The above prints the first item in the output of seq 10 via the if 1..1, while the or eof will also print the last item.

5
  • 3
    Could you explain how this works? Please avoid giving such one-liner answers without an explanation of what they do and how.
    – terdon
    Jun 25, 2014 at 18:35
  • 1
    what do /etc abrt and yum.repos.d have to do this question?
    – drs
    Jun 25, 2014 at 18:48
  • @drs I edited the answer to avoid using 'ls' as sample input.
    – dolmen
    Jun 26, 2014 at 8:58
  • 1
    @dolmen: might be a good idea to finish editing by suppressing all reference to /etc. I guess such was added after yr first edit, but, all the same, as it stands the answer makes no sense at all. Just a suggestion.
    – Cbhihe
    Sep 18, 2016 at 8:42
  • Good answer. I used it for an alias: alias firstlast='perl -ne "print if 1..1 or eof"'. Now I can seq 10|firstlast. With $. containing the line number, replacing 1..1 with $.==1 might be more readable for those unfamiliar with Perl's flip-flop dot-dot operator.
    – Kjetil S.
    Aug 5, 2021 at 9:58
5
$ seq 100 | { IFS= read -r first; echo "$first"; tail -1; }
1
100
1
  • Works great for a sed.... | pipe input!
    – Digger
    Aug 7, 2020 at 20:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .