113

I'm trying to change the message of the day (MOTD) on my Ubuntu Amazon EC2 box so that it will display the git status of one of my directories when I SSH in.

The output from all of the default MOTD files have two spaces at the start of each line so it looks nicely indented, but because my git status output spans several lines, if I do echo -n " " before it only indents the first line.

Any idea how I can get it to indent every line?

6 Answers 6

183

Pipe it to sed to insert 2 spaces at the beginning of each line.

git status | sed 's/^/  /'
Sign up to request clarification or add additional context in comments.

4 Comments

I have no idea how to work around that, but there is a slight problem with this. The exit code of the function is destroyed by sed which might be a problem in some cases.
@cglacet That's true whenever you pipe one command to another. If it's a problem, you redirect the output to a file or variable, test the exit status, then process the saved output.
@cglacet Specifying set -euo pipefail in the second line of your script will test it for you.
If you want to get the exit status of the first command in a pipeline, you can use Bash's built-in variable $PIPESTATUS. It is an array of exit codes for each command in the pipeline. The first command's exit code is ${PIPESTATUS[0]}. Use code similar to: if [ "${PIPESTATUS[0]}" -ne "0" ]; then
67

Building on @Barmar's answer, this is a tidier way to do it:

indent() { sed 's/^/  /'; }

git status | indent
other_command | indent

7 Comments

Doesn't really add much, it was a one-use case inside a shell script and so bash subroutines are a bit overkill, but thanks
@MattFletcher: Well, I think that writing code with subroutines just like this one makes it easier for non-bash prof (like myself) to figure out what is going on. Also, if you decided that you suddenly wanted to have 3 instead of 2 spaces, you have only one place to correct.
@Vering that's true, and I do understand the benefits of such, I was just more suggesting that it could have been a comment rather than an answer itself, as it's pretty much the same answer and addresses an issue that was never brought up in the first place :)
Keep in mind I was providing an alternate code snippet. That is not ideal to depict in a comment given no code formatting. If I was just providing a sentence or two comment about the existing answer that would have been more appropriate in my mind as a comment.
@ChristianHerenz Try indent() { sed "s/^/$(printf "%$1s")/"; }. e.g. indent 4 produces 4 spaces. Pretty neat and works in pure shell too.
|
23

Thanks to @Barmar and @Marplesoft for some nice simple solutions - here is another variation that others might like - a function you can tell how many indent levels using pr:

indent() {
  local indentSize=2
  local indent=1
  if [ -n "$1" ]; then indent=$1; fi
  pr -to $(($indent * $indentSize))
}

# Example usage
ls -al | indent
git status | indent 2

4 Comments

Good answer. FWIW, '$' is unnnecessary inside ((...)). See github.com/koalaman/shellcheck/wiki/SC2004
is there anyway to make this work with Bash colour escapes?
@Sled In case of ls you can use the --color=always option: ls -al --color=always | indent. Source: this StackExchange thread
Thanks for sharing this handy pr command! I usually use sed for indentation — I hadn't come across this before :)
5

You can do it without calling any tools like sed:

output=$(git status); echo "  ${output//$'\n'/$'\n'  }."

Or, in a function:

indent()
{
    local unindented
    unindented="$(< /dev/stdin)"
    echo "  ${unindented//$'\n'/$'\n'  }."
}

git status | indent

How it works: Bash parameter expansion replaces every "end of line" to "end of line" + . The first line is not preceded by "end of line", so we prepend to the whole string.

Like most solutions, tabs in the original output, which may look like 8 spaces, are simply prepended with 2 spaces, and the tabs shrinks, and thus it still looks like indented with 8 spaces. This might be desired or not.

Edit: About efficiency:

On my system, my solution is faster than all sed solutions at 50 lines and gets slower at ~100 lines of git status output. CervEd (see comments) has made tests where my solution already starts being slower at ~30 lines after 9999 repetitions. What you want to use depends a lot on the use case: If your input can get large or you need to be POSIX conform, I would use sed. If your input is known to be small or you do not even have sed, I would use this solution.

8 Comments

For multi-line output, this would not prepend space properly, e.g. echo -e “ some text \n some other text \n word \n\t some long explain.” where I only want to keep indentation of “some long explain.” if it spans multi-line.
Looks like last dot is unnecessary (still nice solution!)
@jimmymcheung that is exactly what I describe in my last passage, right?
@Johannes Oh maybe I understood it wrong back in the time.
I have a hard time seeing how this would be more efficient. sed is incredibly lightweight and I highly doubt that bash does this in any way that is more efficient, except perhaps in cases of very small amounts of text, where the performance gain doesn't matter much. It's also not POSIX compatible. I would strongly advice against this route
|
2

Here's a function I wrote to also indent stderr:

function indented {
  local PIPE_DIRECTORY=$(mktemp -d)
  trap "rm -rf '$PIPE_DIRECTORY'" EXIT

  mkfifo "$PIPE_DIRECTORY/stdout"
  mkfifo "$PIPE_DIRECTORY/stderr"

  "$@" >"$PIPE_DIRECTORY/stdout" 2>"$PIPE_DIRECTORY/stderr" &
  local CHILD_PID=$!

  sed 's/^/  /' "$PIPE_DIRECTORY/stdout" &
  sed 's/^/  /' "$PIPE_DIRECTORY/stderr" >&2 &
  wait "$CHILD_PID"
  rm -rf "$PIPE_DIRECTORY"
}

Use it like:

indented git status
indented ls -al

1 Comment

Doing stderr as well, or stderr separately, is a good point -- but you don't have to go through all that work to make it happen. With bash process substitution, you can do "$@" > >(sed 's/^/ /') 2> >(sed 's/^/ /' >&2). This has the benefit of using anonymous pipes instead of named pipes (and therefore not touching the filesystem outside of /proc), plus the benefit of keeping the return code, and will still keep the pattern above where we never use |.
2

The following function run_indented runs the passed command line and indents the standard outputs STDOUT and STDERR with $INDENT (default: ):

run_indented() {
  local indent=${INDENT:-"    "}
  { "$@" 2> >(sed "s/^/$indent/g" >&2); } | sed "s/^/$indent/g"
}

Example with default indent:

run_indented bash <<'EOF'
    echo "foo" >&2
    echo "bar"
EOF

Prints:

    foo
    bar

Example with custom and nested indent:

export -f run_indented

INDENT='- ' run_indented bash <<'EOF'
    echo "foo" >&2
    echo "bar"

    # The following run_indented only works if
    # it was exported as in the first line.
    run_indented bash -c '
      echo "foo2" >&2
      echo "bar2"
    '
EOF

Prints:

- bar
- foo
- - foo2
- - bar2

Bonus: Keeping the formatting

Some tools check if they are attached to a terminal and only output formatting / colors, if that's the case.

Indentation solutions based on piping will likely cause the indented tool to not see a terminal, and your colors are gone.

If you want to keep the formatting, try the following version:

run_indented() {
  local indent=${INDENT:-"    "}
  local indent_cmdline=(awk '{print "'"$indent"'" $0}')

  if [ -t 1 ] && command -v unbuffer >/dev/null 2>&1; then
    { unbuffer "$@" 2> >("${indent_cmdline[@]}" >&2); } | "${indent_cmdline[@]}"
  else
    { "$@" 2> >("${indent_cmdline[@]}" >&2); } | "${indent_cmdline[@]}"
  fi
}

This version attempts to make your invoked command see a terminal if there actually is one. That is, no matter if your script is part of a pipe or not, it should behave as expected.

Yet, the function can't guarantee the indentation, because prefixing each lines with spaces doesn't stop carriage returns (\r) from jumping back to the start of the line. Also there's a plethora of ways to move the cursor ahead of the indentation.

If it's only for the visuals, the DEC Private Mode Set (DECSET) with the Set Left and Right Margins (DECSLRM) function would be perfect to actually increase the terminals left margin. Unfortunately—at least when I tested it—changing to and back from that mode clears the screen so that you won't be able to see the indented output.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.