Can sed replace text with a string formatted like printf's formatted printing?

The following sed command replaces a line starting with the current value of "$domain" with several values specified in variables.

/bin/sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1\n${domain} ${limittype} ${limititem} ${value}/" /etc/security/limits.conf

However the output will not be properly aligned because the length of the values of domain etc. are not the same.

So the output would be something like

#oracle   hard   nproc    131072
oracle hard nproc 666

While valid, it is difficult to read. I would prefer to get something like

#oracle   hard   nproc    131072
oracle   hard   nproc    666

The best I can come with to get the desired output is:

/bin/sed  "s/\(^${domain}\)\( *\)\(${limittype}\)\( *\)\(${limititem}\)\( *\)\(.*\)/$EXPL#\1\2\3\4\5\6\7\n${domain}\2${limittype}\4${limititem}\6${value}/" /etc/security/limits.conf

But I believe there must a more elegant way to do this.

The sed one liners document contains some examples that use a specified number of characters, e.g.

sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # set at 78 plus 1 space

But this is in the regexp section not in the replacement section.

  • use tabs instead of spaces in the replacement?
    – Kevin
    May 31, 2012 at 13:27
  • Can you please provide an example input section and its corresponding preferred output? Your $EXPL#\1\n bit seems wrong.
    – Mikel
    Jun 1, 2012 at 3:16
  • No. sed can't do this in any way that is practical. If you can provide a clearer description of the problem, many people here can provide an alternative solution using bash, awk, or any number of other tools.
    – Mikel
    Jun 1, 2012 at 3:18

3 Answers 3


While you can theoretically do this entirely in sed (since it's Turing-complete), this isn't the right tool for the job.

An easy way is to insert tabs in sed, then post-process them to spaces. If you can determine the position of all columns, pipe the sed output through expand.

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1\n${domain}\t${limittype}\t${limititem}\t${value}/" |
expand -t 10,17,26

(Use a literal tab character instead of \t if your sed doesn't support \t.)

If you don't know the column widths in advance, try the BSD column utility. It looks at the whole input file to determine column widths that accommodate the length of all rows.

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1 ${domain} ${limittype} ${limititem} ${value}/" |
column -t

If your sed script rewrites both commented-out lines and non-commented-out lines, or if you use column, you'll need a bit of post-processing to skew the commented-out lines by the width of the comment marker.

… | sed '/^#/ s/ //'

You can use awk instead. It has a printf function. As an added bonus, there is an easy way to protect special characters such as . or * in a searched-for column content.

</etc/security/limits.conf awk -v domain="$domain" -v limittype="$limittype" -v limititem="$limititem" -v value="$value" '
$1 == domain && $2 == limittype && $3 == limititem  {
    printf "#%-9s %-8s %-9s %s\n%-9s %-8s %-9s %s\n", $1, $2, $3, $4, $1, $2, $3, value; next
1 {print}
  • Or possibly column -t.
    – Mikel
    Jun 1, 2012 at 3:16
  • I'll play with the awk version a bit, just as an excercise but right now I can't get it to work. Thanks for the elaborate answer!
    – Bram
    Jun 1, 2012 at 7:42
  • @Mikel Oh, thanks for remininding me. I meant to say “expand if you know the width, column if you don't” and forgot to write a paragraph. Jun 1, 2012 at 7:50
  • @Bram I missed out a bit in the awk snippet to print out the input if it doesn't match, try now. Jun 1, 2012 at 7:50
  • @Gilles: I edited the answer, removing the check on value because $value contains the new value and the proposed version would never match a line. I also added an extra line of input so a comment would be written showing the old value. Thanks again.
    – Bram
    Jun 1, 2012 at 8:20

This uses the extended regex syntax -r, which clears up a lot of the clutter. Also, because you already know some of the field values, you don't actually need to back-reference them, again reducing clutter (and overhead).

& is a special replacement value: it hold the entire matched pattern. Using the &, again reduces clutter. As it is not a back-reference, it has significantly less overhead.

I've used ( +) vs. ( *). The + assumes that there is at least one space between input fields. Just change it to the * it that is not the case.


echo "oracle   hard   nproc    131072" |
  sed -r "s/^$dom( +)$typ( +)$itm( +).*/$EXPL#&\n$dom\1$typ\2$itm\3$val/" 


#oracle   hard   nproc    131072
oracle   hard   nproc    666
  • Great! Does exactly what I want and thanks for the explanation on &.
    – Bram
    Jun 1, 2012 at 7:20

Just use printf to format sed output:

printf "%5s %12s %4s\n" $(sed 's/.../.../')
  • 1
    I've seen people embed printf into sed, like sed 's/xxx/$(printf "%5s %12s %4s\n")yyy/' but is it doing a sub-process for each line processed? If so then doing the OPs problem in awk, using it's printf feature probably makes more sense. Good luck to all
    – shellter
    May 31, 2012 at 18:14

You must log in to answer this question.

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