0

I have a variable containing a multiline string.

I am going to interpolate this variable into another multiline echoed string, this echoed string has indentation.

Here's an example:

ip_status=`ip -o addr | awk 'BEGIN {  printf "%-12s %-12s %-12s\n", "INTERFACE", "PROTOCOL", "ADDRESS"
                                      printf "%-12s %-12s %-12s\n", "---------", "--------", "-------" }
                                   {  printf "%-12s %-12s %-12s\n", $2, $3, $4 }'`

echo -e "
    ->
    $ip_status
    ->
"

When running that, the first line of $ip_status is left justified against the ->, however the subsequent lines are not justified against the ->.

It's easier to see if you run that in your bash. This is the output:

    ->
    INTERFACE    PROTOCOL     ADDRESS
---------    --------     -------
lo           inet         127.0.0.1/8
lo           inet6        ::1/128
eth0         inet         10.0.2.15/24
eth0         inet6        fe80::a00:27ff:fed3:76c/64
    ->

I want all the lines in the $ip_status to be aligned with the ->, not just the first line.

CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98

3 Answers3

3

You need to insert the indentation yourself. Bash comes with no feature for making text pretty, although there are some possibly useful utilities (column -t is frequently useful in this sort of application, for example).

Still, inserting indentation isn't too difficult. Here's one solution:

echo "
    ->
    ${ip_status//$'\n'/$'\n    '}
    ->
"

Note: I removed the non-standard -e flag because it really isn't necessary.

Another alternative would be to apply the replacement on the entire output, using a tool like sed:

echo "
 ->
   $ip_status
   ->
" | sed 's/^ */    /'

This second one has the possible advantage that it will tidy up the indentation, even if it were ragged as in the example. If you didn't want that effect, use 's/^/ /' instead.

Or a little shell function whose first argument is the desired indent and whose remaining arguments are indented and concatenated with a newline after each one:

indent() {
  local s=$(printf '%*s' $1 "")
  shift
  printf "$s%s\n" "${@//$'\n'/$'\n'$s}"
}
indent 4 '->' "$ip_status" '->'

That might require some explanation:

  • printf accepts * as a length specifier, just like the C version. It means "use the corresponding argument as the numeric value". So local s=$(printf '%*s' $1 "") creates a string of spaces of length $1.

  • Also, printf repeats its format as often as necessary to consume all arguments. So the second printf applies an indent at the beginning and a newline at the end to each argument.

  • "${@/pattern/subst}" is a substitution applied to each argument in turn. Using two slashes at the beginning ("${@//pattern/subst}") makes it a repeated substitution.

  • $'\n' is a common syntax for interpreting C-style backslash escapes, implemented by bash and a variety of other shells. (But it's not available in a minimal posix standard shell.)

  • So "${@//$'\n'/$'\n'$s}" inserts $s -- that is, the desired indentation -- after every newline in each argument.

rici
  • 234,347
  • 28
  • 237
  • 341
  • Your first example doesn't seem to work. It doesn't keep the indentation. – CMCDragonkai Jun 03 '14 at 05:21
  • Why does the @ need to `//` after it? Why does it need 2 forward slashes and what is the shift for? – CMCDragonkai Jun 03 '14 at 05:33
  • Oh shift is for essentially ignoring the first number. – CMCDragonkai Jun 03 '14 at 05:34
  • @CMCDragonkai: Sorry, there was a small typo in the first example (I wrote `'$\n'` instead of the correct `$'\n'`). It's fixed now. The answer includes an explanation of `${@//`. You'll find more details in the bash manual: http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion – rici Jun 03 '14 at 16:11
1
echo  "    ->"
while IFS= read -r line
do
    echo "    $line"
done <<< "$ip_status"
echo  "    ->"

You can read the variable line by line and echo it with the number of spaces you need before it. I have used the accepted answer of this question.


To make it a function:
myfunction() {
echo  "    ->"
while IFS= read -r line
do
    echo "    $line"
done <<< "$1"
echo  "    ->"
}

myfunction "$ip_status"
Community
  • 1
  • 1
a5hk
  • 7,532
  • 3
  • 26
  • 40
1

A simple form is to use readarray, process substitution and printf:

readarray -t ip_status < <(exec ip -o addr | awk 'BEGIN {  printf "%-12s %-12s %-12s\n", "INTERFACE", "PROTOCOL", "ADDRESS"
                                      printf "%-12s %-12s %-12s\n", "---------", "--------", "-------" }
                                   {  printf "%-12s %-12s %-12s\n", $2, $3, $4 }')

printf '    %s\n' '->' "${ip_status[@]}" '->'

Reference: http://www.gnu.org/software/bash/manual/bashref.html

konsolebox
  • 72,135
  • 12
  • 99
  • 105