hello space
    more spaces

This is my file and I would like to make this with sed:

-hello space
----more spaces

Do I have to use loop to make it or does it exist any different approach? I tried this:

user:~$ sed -n  '
: loop
  s/^ /-/
  s/[^-] /-/p
t loop' spaces

4 Answers 4


With sed, you'd need either a loop like:

sed -e :1 -e 's/^\( *\) /\1-/; t1' < file

Or do something like:

sed '
s/ */&\
/; # add a newline after the leading spaces
h; # save a copy on the hold space
y/ /-/; # replace *every* space with -
G; # append our saved copy
s/\n.*\n//; # remove the superflous part' < file

With perl, you can do things like:

perl -pe 's{^ *}{$& =~ y/ /-/r}e' < file


perl -pe 's/(^|\G) /-/g' < file

\G in PCRE matches (with zero-width) at the end of the previous match (in //g context). So here, we're replacing a space that follows either the beginning of the line ^ or the end of the previous match (that is, the previously substituted space).

(that one would also work with sed implementations that support PCREs like ssed -R).

With awk, you can do something like:

awk '
  match($0, /^ +/) {
    space = substr($0, 1, RLENGTH)
    gsub(" ", "-", space)
    $0 = space substr($0, RLENGTH+1)
  {print}' < file

If you want to convert tabs as well (where for instance <space><tab>foo would be converted to --------foo), you can preprocess the input with expand. With GNU expand, you can make it expand -i so that only the tabs among the leading blanks in the line are converted. You can specify how far apart the tab-stops are (every 8 columns be default) with the -t option.

To generalise that to all horizontal spacing characters, or at least those that are in the [:blank:] category in your locale, that becomes more complicated.

If it weren't for the TAB character, it would just be a matter of:

perl -Mopen=locale -MText::CharWidth=mbswidth -pe 's/^\h+/"-" x mbswidth($&)/e'

But the TAB character being a control character has a width of -1 with that mbswidth(), while in reality it has a variable width from 1 to 8 columns depending on where it's found on the line.

The expand command takes care of expanding it to the right number of spaces, but several implementations, including GNU expand don't get it right when there are multi-byte characters (like all the blank characters except tab, space in UTF-8 locales), and even some of those that support multi-byte characters can be fooled by zero-width or double-width characters (like U+3000 which is in the [:blank:] class in typical GNU locales at least). So one would have to do the TAB expansion by hand like:

perl -Mopen=locale -MText::CharWidth=mbswidth -pe 's{^\h+}{
  $s = $&;
  while ($s =~ /(.*?)\t(.*)/) {
    $s = $1 . (" " x ((7-mbswidth($1)) % 8 + 1)) . $2;
  "-" x mbswidth($s)}e'
  • 1
    I think that perl -pe 's/^([ ]+)/"-" x length $1/e' conveys intent a little better when it comes to a perl solution.
    – hobbs
    May 10, 2017 at 22:36
  • @hobbs. That works too. That's replace the sequence of spaces at the beginning with as many "-"s as there were characters in that sequence of blanks (I don't find using a capture group instead of $& helps with legibility though). I find that the s{^ *}{$& =~ tr/ /-/r}e (replace the sequence of spaces at the beginning with itself after translation of space -> "-") is a more direct translation of the requirements though. May 11, 2017 at 7:39

Stephane has already provided the proper sed solution. Here's a small and a little more explicit Python 3 alternative:

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as f:
    for line in f:
        beginning = True
        for char in line:
            if beginning and char == " ":
               beginning = False

Test run:

# This is the input text
$ cat -A input.txt
 hello space$
    more spaces$

# And this is the output with the given python script
$ ./add_dashes.py ./input.txt                                                                                            
-hello space
----more spaces

Another awk approach:

awk 'match($0, /^[[:space:]]+/){ p=""; l=RLENGTH; while(l--) p=p"-";
     sub(/^[[:space:]]+/,p); print}' yourfile

The output:

-hello space
----more spaces

match($0, /^[[:space:]]+/) - matches sequence of leading spaces

l=RLENGTH - the size of the matched sequence for each line

while(l--) p=p"-" - constructing the replacement substring

Alternative Python 3.x approach:

space_to_hyphen.py script:

import sys, re
with open(sys.argv[1], 'r') as f:  # reading input file
    for l in f.read().splitlines():
        m = re.match(r'^ +', l)    # capture sequence of leading spaces 
        print(l if not m else l.replace(' ', '-', m.end()))


python3 space_to_hyphen.py yourfile
  • It's use [[:blank:]] instead of [[:space:]]. With [[:space:]], you'd convert the CR of blank lines of MSDOS formatted text files ([[:space:]] is horizontal or vertical spaces, [[:blank:]] is horizontal only). May 11, 2017 at 7:44
  • @StéphaneChazelas, feel free to upvote my answer May 11, 2017 at 7:45
  • Note that even after s/space/blank/, that would still convert TAB or " " (U+3000) to a single dash, changing the displayed width of the line (which may or may not be desired, I'm just pointing it out) May 11, 2017 at 8:48
  • @StéphaneChazelas, there's no s/space/blank/ within my approaches as you noted. And they work fine for the current input May 11, 2017 at 9:02
  • Sorry for the confusion. I meant that even if you replaced [[:space:]] with [[:blank:]] (as I'm not sure replacing vertical spacing characters make sense here), that might not do what people want for characters like TAB or U+3000 that are not single-width characters (but are matched by [[:blank:]] and [[:space:]] in some locales). The OP's sample has only ASCII SPC characters, so it won't make any difference whether one uses " ", [[:space:]], [[:blank:]] (or even [^[:print:]]), I'm just flagging it as a note for people coming here with a similar requirement. May 11, 2017 at 10:04


We setup a do-while loop and keep converting the last space adjacent to the first non-space while the line still has a leading space.

sed -e '
      /^ /s/ \([^ ]\|$\)/-\1/
' filename.ext

while IFS= read -r l; do
   read -r ll <<<"$(printf '%ss\n' "$l")"
   printf '%s%s\n' \
      "$(seq -s= 0 "$(expr "$l" : '[   ]*')" | tr = - | tr -cd -)" \
done < filename.ext


-hello space
----more spaces


  • Setup a while loop to read the file line-by-line with an IFS set to NULL. This has the purpose of preserving all whitespace in the line.
  • Next do a dummy read of the same line with the default IFS. This will clip any leading whitespace. We add a dummy nonnewline char at the end to prevent collapse of the trailing newlines in command expansion stage. We strip it away at the time of printing.
  • The expr command has the purpose of finding the number of matches, in our case, whitespace at the leading edge of the line.
  • Using this number we generate a seqence of dashes by means of seq and tr commands appropriately setup.
  • Finally we print the dashes alongwith the trimmed line, i.e., line read in via the default IFS.

You must log in to answer this question.

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