How to read first and last line from cat output?

239,172

Solution 1

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}

Solution 2

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

Solution 3

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='()'

Solution 4

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;}'

Solution 5

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.

Share:
239,172

Related videos on Youtube

dmgl
Author by

dmgl

Updated on September 18, 2022

Comments

  • dmgl
    dmgl over 1 year

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

  • Stéphane Chazelas
    Stéphane Chazelas almost 10 years
    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.
  • Stéphane Chazelas
    Stéphane Chazelas almost 10 years
    In the first one, the order of the lines is not guaranteed.
  • dmgl
    dmgl almost 10 years
    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
    dmgl almost 10 years
    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
    dmgl almost 10 years
    @StéphaneChazelas Thank you for this Note! Demo: file with only 1. Command $ cat file | sed '1p;$!d' #return 1 1 and $ cat file | awk 'NR==1; END{print}' return 1 1
  • gerrit
    gerrit almost 10 years
    Can we generalise this if we replace 'file' by an arbitrary process writing to stdout?
  • chaos
    chaos almost 10 years
    @gerrit i added your suggestions
  • mikeserv
    mikeserv almost 10 years
    @chaos - though in almost every situation it would not matter, there are certain rare cases in which you might need to ensure you always get the first and last line directly following one another. With sed you can do 1h;$!d;x;G;1g - that way the output of the first and last lines will always instantaneously follow one another, and in the event of a single line input you'll always only get the singe line out.
  • terdon
    terdon almost 10 years
    Could you explain how this works? Please avoid giving such one-liner answers without an explanation of what they do and how.
  • drs
    drs almost 10 years
    what do /etc abrt and yum.repos.d have to do this question?
  • David Conrad
    David Conrad almost 10 years
    Can you explain the sed one(s)? Even after a look at the sed man page, I'm still confused (this is a common occurrence for me with the sed man page).
  • dolmen
    dolmen almost 10 years
    @drs I edited the answer to avoid using 'ls' as sample input.
  • Angel Todorov
    Angel Todorov almost 10 years
    Does head && tail really work for you? seq 10 | (head -n1 && tail -n1) prints 1 only.
  • chaos
    chaos almost 10 years
    @glennjackman That annoyed me too a bit. The head && tail part is not always working. Switch to line buffered stream, then it works: stdbuf -oL seq 10 | (head -n1 && tail -n1) print 1 and 10 for me.
  • Angel Todorov
    Angel Todorov almost 10 years
    @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.
  • Cbhihe
    Cbhihe over 7 years
    @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
    Cbhihe over 7 years
    @mikeserv: Why do you use 1g at the end of your sed script ? Using it crushes the contents of the pattern space with that of the hold space, which is just the last line. Shouldn't you be already well served by seq 10 | sed '1h;$!d;x;G' ?
  • mikeserv
    mikeserv over 7 years
    @cbhihe - not sure - just on my phone w/ 3% charge and only a single sock, but i would guess it is to prevent getting the first line twice if input is only a single line
  • Cbhihe
    Cbhihe over 7 years
    @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.
  • mikeserv
    mikeserv over 7 years
    @cbhihe - you know i would check it, but the CVPD has my computer on a $150 hock and the rest of my things burned up with my house when they torched it. its a weir'd life.
  • mikeserv
    mikeserv over 5 years
    @cbhihe - that expression does $!d... so the only case when a 1g should crush the output is a case of a oneline input file, barring a continued live scenario with more than one last line...
  • Cbhihe
    Cbhihe over 5 years
    @mikeserv: yup. agreed ! But talk about long lived thread and necroposting... 4 yrs and going strong. Hope all yr woes from back in Sep.'16 are behind you.
  • mikeserv
    mikeserv almost 5 years
    @cbhihe - the mad hatter said unbirthdays make us young again. the dormouse helped him drink his wine. but i say, if ! kill -0 $$; then ! $0 -ba</; fi
  • Kjetil S.
    Kjetil S. over 2 years
    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.
  • crsuarezf
    crsuarezf about 2 years
    @glennjackman try with (head -n1 && tail -n1) <<< $(seq 10)