To delete the line with # , the following line can work:

sed -i "/^\(\\s\)*\\#/d"

but what I want is to delete lines that start with # and not with #!.


sed -i -e '/^\s*#\([^!]\|$\)/d'


  • ^ start of line
  • \s* zero or more whitespace characters
  • # one hash mark
  • \([^!]\|$\) followed by a character which is not ! or end of line.
sed -i -e '/^#!/p' -e '/^#/d' file

This will go through the file line by line and when it finds a line starting with #! it will be printed by the first expression. Then it will be deleted from the pattern space by the second expression (i.e. it will not be printed a second time by the default p command which is in effect when not using sed -n).

A line starting with just # will be ignored by the first expression, but deleted by the second expression.

Any other line will be printed by the default p command.

To allow for blanks in front of the # (and delete those lines too):

sed -i -e '/^[[:blank:]]*#!/p' -e '/^[[:blank:]]*#/d' file

The [[:blank:]] expression will match a space or tab character.

As Stéphane mentioned is comments, changing p to b in the first expression would allow the sed script to continue with the next line of input without considering the second expression, if the first expression matches. The b command branches to a pre-defined label, or to the end of the sed script if no label is given. This would be an optimization.

  • 2
    See b instead of p to branch off. – Stéphane Chazelas Jun 29 '17 at 10:06
  • @StéphaneChazelas Yes, that would be a small possible optimization. – Kusalananda Jun 29 '17 at 10:08
  • Maybe comment that only the first line could be a shebang and that this modification of your code does work on that case: sed -e '1{/^#!/p}' -e '/^\s*#/d' file – Arrow Jun 30 '17 at 19:41
  • Possibly, but there may be text files that are scripts that contain here-documents that are also scripts... – Kusalananda Jun 30 '17 at 19:41

To "and" two addresses, you need a command group ({...;}):

sed '/^[[:space:]]*#/{/^#!/!d;}' < file

With GNU sed, you can use -i for inplace, replace [[:space:]] with \s (assuming a recent version) and omit the ;:

sed -i '/^\s*#/{/^#!/!d}' file

You can nest several, but beware that portably, you can't have anything after the }. So for A and B and not C and not D, that would be:

sed '/A/{/B/{/C/!{/D/!d;}' -e '}' -e '}' < file


sed '
  }' < file
  • Up-voted for educational value. Teach a man how to fish and all that… – David Foerster Jun 29 '17 at 19:20
  • Why the redirection < file if sed could read the file? – Arrow Jun 30 '17 at 18:29

Use the negative look-ahead in Perl:

perl -ne 'print unless /#(?!!)/'

This removes lines containing # not followed by !. If you want to match the # only at the beginning of a line, probably preceded by whitespace, use

perl -ne 'print unless /^\s*#(?!!)/'

Abstract: remove all non-shebang comments.

sed -e '1{/^\s*\#/{/^\#!/!d}}' -e '1!{/^\s*\#/d}' file

Your command modified to use single quotes (no double \\):

sed '/^\(\s\)*\#/d'

will almost work correctly by just adding the detail that (after the #) there must be anything that is not an asterisk [^!] sed '/^\(\s\)*\#[^!]/d'. But that will fail with a line that is empty after the comment (#`) character.

For that we need to assert that the line has ended ($).

For that we will need the use of extended syntax ([^!]|$):

sed -E '/^\s*\#([^!]|$)/d'

Or, more portable:

sed -E '/^[ \t]*\#([^!]|$)/d'

However, for an script, only the first line that start as #! matters.
All other lines that start with optional space and a # are comments:

sed -e '1{/^#!/!d}' -e '1!{/^[ \t]*#/d}' file

Which means:

First -e

if the first line start with a comment (space and #) but does not start exactly with #! it is erased.

Second -e

other lines (1!) that start with an optional space and # are removed.

  • For completeness, -r is supported by GNU sed since 3.01 (1998) (initially --rxtest, not documented, changed to --regexp-extended and documented in 3.95 in 2002) -E added for compatibility with BSDs in 2009 (4.2), documented in 4.3. -E in FreeBSD since 2000 (-r for GNU compatibility in 2010), NetBSD since 2002 (-r in 2008), OpenBSD both -r and -E in 2009. busybox -r in 2004, -E in 2014. -E is the one that will be added to POSIX (and why -E was added to busybox and documented in GNU). – Stéphane Chazelas Jun 30 '17 at 5:49

This worked for me:

$ cat test.sed 
  # delete this one

  #! don't delete this one

        # delete this too


$ sed -i -e '/^[[:space:]]*#[^!].*/d' test.sed


$ cat test.sed 

  #! don't delete this one

Well, this command won't delete lines with just a lonely #. So, I keep this answer to show why you need to refer to other solutions.

  • 4
    Try it with a lone #. It will not be deleted, whereas it should be per OP. Reason? The [^!] consumes one character. There is no concept to look arounds in sed. – user218374 Jun 29 '17 at 12:30

Presumably you want to delete all comment lines (starting with any amount of white space followed by #) except the she-bang which needs to be in the first line to be detected.

You can use the very same substitution command if you restrict it to only regard everything beginning from line 2; in sed notation that is the range 2,$. You restrict sed commands to a range by prefixing them with that range:

sed "2,${/^\(\\s\)*\\#/d}"

or with shell quotation that allows for more readable back spaces and without unnecessary regular expression grouping:

sed -e '2,${/^\s*#/d}'



# This is the first comment

command 1
  # another comment
  command 2
# Final comment



command 1
  command 2

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.