9

If I have some nested and indented statements that echo a multi-line string into a file, the indents from the original shell (in my case bash) will be carried over into the target file.

To avoid this, I remove indents from the echo'ed output, but this loses that indented code formatting in my source, for example,

#! /bin/bash

function configure_default_vhost() {

    case "$2" in
        [--production])
              echo "<VirtualHost *:80>
# These lines are "breaking" out of the preffered indenting
redirect 404 /
ErrorDocument 404
</VirtualHost>
" > /etc/apache/sites-availabe/000-default.conf
    esac
}

I'm aiming to get something as close as possible to this:

#! /bin/bash

function configure_default_vhost() {

    case "$2" in
        [--production])
              echo "<VirtualHost *:80>
                    # These lines are aligned with the preffered indenting
                    redirect 404 /
                    ErrorDocument 404
                    </VirtualHost>
                    " > /etc/apache/sites-availabe/000-default.conf
    esac
}

(Note: this question has been listed as a possible duplicate of a HEREDOC related question. I'm not sure where the correct place to respond to this is so I'll put here for now (someone please let know if otherwise). My question is about indenting code blocks, and heredoc is only one of the many ways to acheive this and actually HEREDOC wasn't the solution I went with.)

Peter Mortensen
  • 1,029
  • 1
  • 8
  • 10
the_velour_fog
  • 11,840
  • 16
  • 64
  • 109
  • 2
    Try using a heredoc with a minus sign (e.g. `cat <<-EOF`). See http://unix.stackexchange.com/questions/76481/cant-indent-heredoc-to-match-nestings-indent for details. – Mikel Apr 24 '15 at 14:09

3 Answers3

12

You can use a "here-document" with the - modifier. It can be indented by tab characters. You must switch from echo to cat.

cat <<-EOF > /etc/apache/sites-availabe/000-default.conf
        <VirtualHost *:80>
        redirect 404 /
        ErrorDocument 404     
        </VirtualHost>
EOF

Or, to keep tabs in the result, you can pre-process the HERE document by let's say sed and indent with 4 spaces instead:

sed 's/^    //' <<EOF
....{
....(------)func () {
....(------)return
....(------)}
....}
EOF

I used . instead of a space and (------) instead of a tab to show how to format the script.

choroba
  • 45,735
  • 7
  • 84
  • 110
  • nice one, I will test in a script now, is it possible to add back in tabs using `here-document` so that the the resulting file can have tabs/indents? I guess the heredoc `-` modifier only removes a fixed number of indents and therefore you can just write your text with tabs and *those* tab will naturally be included in output? – the_velour_fog Apr 24 '15 at 14:14
  • @user4668401: Unfortunately, all leading tabs are removed. – choroba Apr 24 '15 at 14:19
  • @user4668401: Check the update. – choroba Apr 24 '15 at 14:27
  • + 1, I tried your `sed ` idea to **post** process the target file i.e. `sed -i /\-----\-----\-------//g` and that worked, but with HEREDOC keep getting `error: line delimited by end of file wanted EOF, unexpected end of file` – the_velour_fog Apr 24 '15 at 16:02
  • 1
    @user4668401: The final EOF must be left aligned, unless you use `<<-EOF` and indent the final EOF with tabs only. – choroba Apr 24 '15 at 16:12
  • +1 Avoiding a discussion how you indent, I use `\s+`. Actually I can fill a multiline variable with `my_var=$(sed -r 's/^\s+//' << END` , next lines content, the line with `END` and the next line `)`. – Walter A Jun 03 '21 at 10:00
9

A few approaches:

  • printf '%s\n' > "$conf_file" \
      '<VirtualHost *:80>' \
      '  redirect 404 /' \
      '  ErrorDocument 404' \
      '</VirtualHost>'
    

    If you want escape sequences to be expanded replace %s with %b:

    printf '%b\n' > "$conf_file" \
      '<VirtualHost *:80>' \
      '\tredirect 404 /' \
      '\tErrorDocument 404' \
      '</VirtualHost>'
    
  • @choroba's cat <<- EOF approach, but indentation has to be done with TAB characters only and they're all removed.

  • ...
      indent='
      '; cut -b "${#indent}-" << EOF > "$conf_file"
      <VirtualHost *:80>
        redirect 404 /
        ErrorDocument 404     
      </VirtualHost>
    EOF
    

    (the trick being to have $indent hold the number of characters of indent to trim (beware this time, indenting has to be done with space characters, not tabs)).

  • ...
      cut -d'|' -f2- << EOF > "$conf_file"
      |echo "<VirtualHost *:80>
      |  redirect 404 /
      |  ErrorDocument 404     
      |</VirtualHost>
    EOF
    

    Escape sequences won't be expanded, but variables and command substitutions will. Quote EOF to prevent that (cut... << 'EOF' for instance).

  • You can also trim indentation based on that of the first line:

    show() {
      expand | awk 'NR == 1 {match($0, /^ */); l = RLENGTH + 1}
                    {print substr($0, l)}'
    }
    ...
      show << EOF > "$conf_file"
        <VirtualHost *:80>
          redirect 404 /
          ErrorDocument 404     
        </VirtualHost>
    EOF
    
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
3

Using echo with the -e switch ( enable the interpretation of backslash escapes):

#!/bin/bash

function configure_default_vhost() {
    conf_file="/etc/apache/sites-availabe/000-default.conf"
    case "$2" in
        [--production])
            # The ">" overwrites; the ">>" appends. 
            {
                echo -e "<VirtualHost *:80>"
                echo -e "\tredirect 404 /"
                echo -e "\tErrorDocument 404"
                echo -e "</VirtualHost>"
            } > "$conf_file"
    esac
}
Christopher
  • 15,611
  • 7
  • 51
  • 64
  • @StéphaneChazelas I was just about to ask you that in a comment on your answer, the way of capturing mulitple `echo` or `printf` seems workable but I wondered how you will capture/group all those lines into the final `> file` statement. i.e. I dont want to write `>> $conf_file` on every line. will test `{echo; echo; echo; } > conffile` now – the_velour_fog Apr 24 '15 at 14:27
  • I think the idea was @StéphaneChazelas - but the format in your answer with the `{echo ; echo}` worked for me , thanks – the_velour_fog Apr 24 '15 at 16:23