How to Read Multi-line Input from STDIN into a Variable in Bash (Backspace Handling & Termination)


2 views

When building automation scripts that require user input, properly capturing multi-line text with special characters (like backspace) can be tricky. Here's a robust solution that works across Unix-like systems including macOS.

#!/bin/bash

echo "Enter version notes (press Ctrl+D when done):"
versionNotes=$(cat)
echo "Captured input:"
printf "%s\n" "$versionNotes"

The cat command reads until EOF (Ctrl+D) and properly handles:

  • Backspace and other control characters
  • Arbitrary line lengths
  • Special characters in the input

For cases where you want an explicit end marker:

read -r -d 'END' versionNotes <<'EOF'
first line
second line with special chars !@#
third line
END

For your specific use case of writing to application.yml:

{
echo "version_notes: |"
sed 's/^/  /' <<< "$versionNotes"
} >> source/application.yml

1. Leading/trailing whitespace preservation:
versionNotes=$(cat; echo x)
versionNotes=${versionNotes%x}

2. Handling tabs and special indentation:
Use IFS= to prevent trimming


When building automation scripts on macOS (including legacy 10.6.8 systems), properly capturing multi-line user input can be surprisingly tricky. Let's explore several robust approaches with their trade-offs.

This method uses a delimiter to mark input termination:

read -r -d '' versionNotes <<'END'
# User pastes/pastes multi-line content here
# Supports backspacing normally
END

Key advantages:

  • Preserves all formatting including indentation
  • Handles special characters correctly
  • Allows normal text editing during input

For complex input scenarios, spawning a temporary editor works best:

#!/bin/bash
tempfile=$(mktemp)
${EDITOR:-nano} "$tempfile"
versionNotes=$(<"$tempfile")
rm "$tempfile"

# Append to config
echo "version_notes: |" >> source/application.yml
awk '{print "  "$0}' <<< "$versionNotes" >> source/application.yml

A more controlled approach for line-by-line processing:

echo "Enter version notes (empty line to finish):"
while IFS= read -r line; do
  [[ $line ]] || break
  versionNotes+="$line"$'\n'
done

printf "Captured notes:\n%s" "$versionNotes"

For backward compatibility with older Bash versions (pre-4.0):

{
  echo "version_notes: |"
  while IFS= read -r line; do
    echo "  $line"
  done
} > source/application.yml

Common pitfalls to avoid:

  • Always use -r with read to prevent backslash interpretation
  • Set IFS= to preserve leading/trailing whitespace
  • Use $'\n' for explicit newlines in variable concatenation