Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

deleting (i.e) not printing lines Here are the examples for not printing the 4th line:

sed: sed -n '4!p'

awk: awk 'NR != 4'

perl: perl -ne '$. != 4 && print'

Not much between them really.



sort by: page size:

It's a lot easier to delete specific lines using sed. Also you can have sed do replacements to the n'th instance of something. Doing that in Perl is a bit more complicated and a lot less succint.

$ echo "foo foo foo foo" | sed 's/foo/bar/3'

foo foo bar foo


> Print one line

> > sed -n '10p' myfile.txt

Which line? Why p? I can make assumptions that this means “line 10, print” but easy-to-explain could go a small step further and complete the explanation.


One of these days I need to get around to learning awk. In the meantime, I've learned some of the deeper, stateful, features of sed. For instance, you mentioned wanting to only output a line if it was preceded by another. Here's a sed command that does so:

    sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'

    > echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'
    CURR
This uses sed's hold buffer. I'll break it down:

    sed -n
The `-n` tells sed no to print anything out. By default, sed prints out whatever is left when processing. We'll tell it with the `p` command when to do so.

    sed -ne 'x'
`-e` indicates we are specifying one of the scripts sed will execute. The command `x` switches the current line with whatever is in the hold buffer. We'll do this on every line.

    sed -ne 'x' -e '/PREV/
The next command will only run on lines that contain `PREV`. But, because we've been putting lines in the hold buffer, we'll only execute on lines after `PREV` when it has been switched out of the hold buffer.

    sed -ne 'x' -e '/PREV/ { ... }'
The braces indicate all commands should be run when we see this match.

    sed -ne 'x' -e '/PREV/ { x; ... }'
First, we switch the hold buffer with the line buffer.

    sed -ne 'x' -e '/PREV/ { x; /CURR/ p; ... }'
Then, we only print out the line if it contains CURR.

    sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'
Finally, we switch them back in case there is overlap in our matches. (Give `echo -e "PREV\nPREVCURR\nCURR\nCURR\nPREV\nRED"` a try with this.)

All that said, I'm pretty sure the `awk` script is much simpler and more direct, but I wanted to share how one might accomplish this was sed.

The time I spent learning this probably would've been better spend on awk, but this tutorial[0], was so good and so easy, it taught me nearly everything I know about sed.

[0]: https://www.grymoire.com/Unix/Sed.html


True, but if the file isn't that big, then the difference isn't noticable.

Being able to do something like

  /some string/-2 d
Which deletes a line 2 lines above where some string matched is something that's trivial to do in ed, but takes a bit of work in sed.

Yep. Need line 123425938039 of a gigantic file? [0]

    sed -e 'NR == 123425938039 { print; exit; }' < file
Fast as hell and takes no memory to speak of. Google 'awesome awk' for goodies.

[0]: Yes, this happens.


For my & others' reference (because I keep having to look it up, just like parent):

    perl -ne "print $_;"
'-n' will run the expression over each line of the input. This is the Awk-like mode.

    perl -pe "s/foo/bar/"
'-p' will run the expression over each line of input, and print out the (possibly modified) line. This is the Sed-like mode.

Slightly more info available in `man perlrun` or https://perldoc.perl.org/perlrun.html


Hah, lol, thanks, Today I Learnt ;) Will try using this next time, probably.

That said, there's one extra advantage with the awk script, that by rearranging the expressions appropriately, I can choose whether to include or exclude the the first and last line in the output (i.e. 'FIRST; p; LAST' vs. 'FIRST; LAST; p' or 'p; FIRST; LAST', etc.) Is this also possible with sed? :)


You can get the same behaviour with sed without "programmability":

  $ sed -n '/^func Test/,/^}/p' file
Which means: match 'func Test' at the beginning of a line and print (p command at the end) until you find a line beginnning with '}'.

This is because the print command (p) accepts 2 addresses to delimit a range. I used regexes as addresses but, for instance, can use line numbers also:

  $ sed -n '10,20p' file
This prints file's line from 10 to 20.

By default, sed prints the pattern space (modifications to each line) at the end of the script. The -n I used is to avoid that.


Exactly. Use Perl (or maybe Python).

sed is bullshit.

Do you know it is not actually possible to get output from sed that does not contain a newline?


Even though, the ruby one-liner seems more legible, it is a lot longer (and probably a lot slower).

In this case I don't see what's wrong with the sed, awk or grep commands (fast and short).

To explain how they work

    sed -i '/^$/d' input.txt # for every line matching '^$' (empty line) apply 'd' (delete the line).
    grep -v '^$' # print every line not matching '^$' (empty line)


    awk 'NF' input.txt
    # 'NF' stand for Number of Fields (this is equivalent to number of columns
    # in a table). If the number of fields is 0 the line must be empty.  In awk
    # 0 is false and non-zero is true. Thus when 'NF' is non-zero (non-empty)
    # it is true, and the line will be printed.

NP. You may know this, but there is also 'sed nq' where n is a number.

E.g.:

sed 15q file.txt

which is like the 'head' command - prints the 1st 15 lines of file.txt and then stops.


I really need to invest more time into Sed/Awk. I typically only use them for find/replace or deleting characters/lines in my shell scripts.

The "print lines by number" was there because earlier versions of ack had a `--line=N` feature, where you could say "ack --line=15-18" and print those four lines. I dropped it because it was hardly any better than using sed.

If you've got suggestions on improvements, please submit an issue. I'd love to hear them.

https://github.com/beyondgrep/website/issues


Instead of:

   sed -n "/$label:/{:a;n;p;ba};"
I think it's more idiomatic to do:

   sed -n "/$label:/,$ p"
Or even:

   sed "0,/$label:/,$ d"
Which deletes the label itself, so you don't need the subsequent `grep -v ':$'`, but then you also aren't allowed to put any statements on the same line as the label.

Drive by code review, you can end a line with the pipe (also && and ||) to avoid a backslash.

    some_command |
      grep -E 'some.*regex$' |
      sed -e 's/erase_text//g' |
      sed -e 's/erase_more//g' |
      awk '{ print $2 }' |
      cut -d ':' -f 1

This is such a common problem there's a stackoverflow article on it.

http://stackoverflow.com/questions/17908555/printing-with-se...

(Actually, there's probably more than one despite their really good duplication grooming, but this was the first hit.)


I see. Thank you for the explanation. I didn't know about that feature of sed.

Like so?

    % echo 'one\ntwo\nthree' | sed -e 's,$, four,'
    one four
    two four
    three four

> echo "$FOO" | sed -e ' s/^[[:space:]]//g; s/[[:space:]]$//g '

Your example fails when $FOO is "-n", for instance. Also the g modifiers are redundant in your example since there's only one beginning of line per line and one end of line per line.

I would instead write this:

  sed -r 's/^\s+|\s+$//g' <<< "$FOO"
EDIT: I think I see now what you probably thought would happen by using g, but no it wouldn't remove multiple spaces. So, you example also fails when $FOO is " x" (using 2 or more spaces at the ends).
next

Legal | privacy