|

Linux CLI 55 🐧 shell scripts output text

⏱️ Estimated read: 13 min | |

#!/bin/bash

# Simple echo command to output text
echo "Hello, World!"

greeting="Welcome to Shell Scripting"
echo $greeting

echo "This is a newline:\nAnd this is a tab:\t."

echo -n "Hello, "
echo "World!"

name="Alice"
age=30
printf "Name: %s\nAge: %d\n" "$name" "$age"

number=123.456789
printf "%.2f\n" "$number"  # Output will be "123.46"

cat << EOF
This is a here document.
It can span multiple lines.
EOF

cat << EOF
Name: $name
Age: $age
EOF

Outputting text is the most basic thing a shell script does. You can use echo for simple messages, printf when you need precise formatting, and here documents when you need to pass a block of multi-line text to a command.

Key point: echo is quick and convenient but behaves differently across shells. printf is predictable and portable. Here documents let you feed multi-line input to any command without creating a temporary file.


a – echo in shell scripts

echo is the fundamental tool for outputting text or variables in shell scripting. It prints its arguments to standard output, separated by spaces, followed by a newline.

Basic usage:

$ echo "Hello, World!"
[ Hello, World! ]

$ greeting="Welcome to Shell Scripting"
$ echo $greeting
[ Welcome to Shell Scripting ]

Escape characters:

Use -e to enable escape sequence interpretation:

$ echo -e "This is a newline:\nAnd this is a tab:\t."
[ This is a newline:
And this is a tab:	. ]
EscapeMeaning
\nNewline
\tTab
\\Backslash
\aAlert (bell)
\rCarriage return
\bBackspace
\cSuppress trailing newline

⚠️ Important: The -e flag is not portable. In bash and zsh it works, but in dash (the default /bin/sh on Debian/Ubuntu) echo interprets escapes by default and -e prints a literal -e. For portable scripts, use printf instead.

The -n option — no trailing newline:

$ echo -n "Hello, "
$ echo "World!"
[ Hello, World! ]

Without -n, the two echo calls would print on separate lines:

$ echo "Hello, "
$ echo "World!"
[ Hello, ]
[ World! ]

Printing variables:

$ name="Alice"
$ echo "Hello, $name"
[ Hello, Alice ]

$ echo 'Hello, $name'
[ Hello, $name ]

Single quotes prevent expansion — $name is printed literally.

Common echo uses:

# Blank line
echo

# Message
echo "Starting backup..."

# Variable value
echo "$HOME"

# Command substitution
echo "Today is $(date +%F)"

# Multiple values
echo "Name:" "$name" "Age:" "$age"
[ Name: Alice Age: 30 ]

# Redirect to a file
echo "log entry" >> app.log

# Print to stderr
echo "Error: file not found" >&2

echo vs printf — when to use which:

Aspectechoprintf
SimplicitySimpleMore verbose
PortabilityShell-dependentPOSIX-standard
Escape handling-e (bash), default (sh)Explicit \n
FormattingNoneWidth, precision, types
NewlineAutomaticManual \n
Best forQuick messagesScripts, tables

Rule of thumb: Use echo for quick messages and printf for anything that needs to be portable or formatted.


b – printf and here document in shell scripts

printf — precise control over output format:

printf gives you the same formatting power as C’s printf(). You provide a format string with specifiers, then the values to fill them.

$ name="Alice"
$ age=30
$ printf "Name: %s\nAge: %d\n" "$name" "$age"
[ Name: Alice ]
[ Age: 30 ]

Common format specifiers:

SpecifierPurpose
%sString
%dDecimal integer
%fFloat
%xHexadecimal
%oOctal
%cCharacter
%%Literal %

Width and precision:

$ number=123.456789
$ printf "%.2f\n" "$number"
[ 123.46 ]

$ printf "%10.2f\n" "$number"
[     123.46 ]

$ printf "%-10s|\n" "left"
[ left      | ]

$ printf "%05d\n" 42
[ 00042 ]
FormatMeaning
%.2f2 decimal places
%10.2fWidth 10, 2 decimals
%-10sLeft-align in 10 columns
%05dZero-pad to 5 digits

Building a table:

$ printf "%-10s %-5s\n" "NAME" "AGE"
$ printf "%-10s %-5d\n" "Alice" 30
$ printf "%-10s %-5d\n" "Bob" 25
[ NAME       AGE   ]
[ Alice      30    ]
[ Bob        25    ]

printf does not add a newline — you must include \n:

$ printf "Hello"
[ Hello ] $                    # prompt on same line

$ printf "Hello\n"
[ Hello ]
$

printf vs echo for variables with special characters:

$ var="100% done"
$ echo "$var"
[ 100% done ]

$ printf "$var\n"              # ❌ % is a format specifier!
[ 100 done ]                   # wait, actually this may error or misbehave

$ printf '%s\n' "$var"         # ✅ safe — %s treats it literally
[ 100% done ]

⚠️ Warning: Never use a variable as the format string. Always use printf '%s\n' "$var" to print variables safely.

Here documents — multi-line input:

A here document (heredoc) lets you pass a block of multi-line text to a command’s standard input, directly in the script. No temporary file needed.

Syntax:

command << DELIMITER
line 1
line 2
line 3
DELIMITER

The DELIMITER can be any word — EOF, END, STOP, etc. It must appear alone on a line to end the block.

Example 1 — multi-line text:

$ cat << EOF
This is a here document.
It can span multiple lines.
EOF
[ This is a here document. ]
[ It can span multiple lines. ]

Example 2 — using variables:

$ name="Alice"
$ age=30
$ cat << EOF
Name: $name
Age: $age
EOF
[ Name: Alice ]
[ Age: 30 ]

Variables are expanded inside the heredoc — because the delimiter is unquoted.

Quoting the delimiter — no expansion:

$ name="Alice"
$ cat << 'EOF'
Name: $name
Age: $age
EOF
[ Name: $name ]
[ Age: $age ]

With 'EOF' (quoted), variables are not expanded — the text is literal.

DelimiterVariable expansion
<< EOFYes
<< "EOF"No
<< 'EOF'No
<< \EOFNo

Indented here documents with <<-:

Use <<- to allow leading tabs (not spaces) to be stripped:

$ cat <<- EOF
	This line has a leading tab
	So does this one
	EOF
[ This line has a leading tab ]
[ So does this one ]

Common uses of here documents:

# Feed a multi-line SQL query to mysql
mysql -u root -p << EOF
SELECT * FROM users;
SELECT COUNT(*) FROM orders;
EOF

# Send an email body
mail -s "Report" admin@example.com << EOF
Here is the report for today.
All systems operational.
EOF

# Generate a config file
cat > /etc/myapp.conf << EOF
[server]
host = $HOSTNAME
port = 8080
[logging]
level = info
EOF

# Run a remote script over SSH
ssh user@host << EOF
cd /var/log
ls -lh
df -h
EOF

# Create a file with literal content
cat > script.sh << 'EOF'
#!/bin/bash
echo "Hello from a generated script"
EOF
chmod +x script.sh

Why here documents matter:

  • Pass multi-line input without quoting hell
  • No temporary files
  • Variable expansion when you want it, literal text when you don’t
  • Works with any command that reads stdin

Complete Example Session

# ============================================
# PART 1: BASIC ECHO
# ============================================

$ echo "Hello, World!"
[ Hello, World! ]

$ greeting="Welcome to Shell Scripting"
$ echo $greeting
[ Welcome to Shell Scripting ]

# ============================================
# PART 2: ECHO WITH ESCAPES
# ============================================

$ echo -e "This is a newline:\nAnd this is a tab:\t."
[ This is a newline:
And this is a tab:	. ]

# ============================================
# PART 3: ECHO -N (NO NEWLINE)
# ============================================

$ echo -n "Hello, "
$ echo "World!"
[ Hello, World! ]

# ============================================
# PART 4: PRINTF
# ============================================

$ name="Alice"
$ age=30
$ printf "Name: %s\nAge: %d\n" "$name" "$age"
[ Name: Alice ]
[ Age: 30 ]

$ number=123.456789
$ printf "%.2f\n" "$number"
[ 123.46 ]

$ printf "%05d\n" 42
[ 00042 ]

$ printf "%-10s|\n" "left"
[ left      | ]

# ============================================
# PART 5: PRINTF TABLE
# ============================================

$ printf "%-10s %-5s\n" "NAME" "AGE"
$ printf "%-10s %-5d\n" "Alice" 30
$ printf "%-10s %-5d\n" "Bob" 25
[ NAME       AGE   ]
[ Alice      30    ]
[ Bob        25    ]

# ============================================
# PART 6: HEREDOC — MULTI-LINE
# ============================================

$ cat << EOF
This is a here document.
It can span multiple lines.
EOF
[ This is a here document. ]
[ It can span multiple lines. ]

# ============================================
# PART 7: HEREDOC — WITH VARIABLES
# ============================================

$ name="Alice"
$ age=30
$ cat << EOF
Name: $name
Age: $age
EOF
[ Name: Alice ]
[ Age: 30 ]

# ============================================
# PART 8: HEREDOC — NO EXPANSION
# ============================================

$ cat << 'EOF'
Name: $name
Age: $age
EOF
[ Name: $name ]
[ Age: $age ]

# ============================================
# PART 9: HEREDOC — GENERATE A SCRIPT
# ============================================

$ cat > greet.sh << 'EOF'
#!/bin/bash
echo "Hello from a generated script"
EOF

$ chmod +x greet.sh
$ ./greet.sh
[ Hello from a generated script ]

# ============================================
# PART 10: HEREDOC — CONFIG FILE
# ============================================

$ cat > /tmp/myapp.conf << EOF
[server]
host = $HOSTNAME
port = 8080

[logging]
level = info
EOF

$ cat /tmp/myapp.conf
[ [server] ]
[ host = olympos ]
[ port = 8080 ]
[ ]
[ [logging] ]
[ level = info ]

# ============================================
# PART 11: HEREDOC — REMOTE COMMANDS
# ============================================

$ ssh user@host << EOF
cd /var/log
ls -lh
df -h
EOF

# ============================================
# PART 12: HEREDOC — SQL
# ============================================

$ mysql -u root -p << EOF
SELECT COUNT(*) FROM users;
SELECT * FROM orders WHERE total > 100;
EOF

Quick Reference

echo

CommandPurpose
echo "text"Print text
echo $VARPrint variable
echoPrint blank line
echo -n "text"No trailing newline
echo -e "a\nb"Interpret escapes
echo "text" > fileRedirect to file
echo "text" >> fileAppend to file
echo "error" >&2Print to stderr

echo — Escapes (with -e)

EscapeMeaning
\nNewline
\tTab
\\Backslash
\aBell
\rCarriage return
\bBackspace
\cSuppress newline

printf — Specifiers

SpecifierPurpose
%sString
%dInteger
%fFloat
%xHex
%oOctal
%cCharacter
%%Literal %

printf — Formatting

FormatMeaning
%.2f2 decimals
%10.2fWidth 10, 2 decimals
%-10sLeft-align width 10
%10sRight-align width 10
%05dZero-pad to 5
%bString with escapes

Here Documents

SyntaxBehavior
<< EOFExpand variables
<< 'EOF'No expansion
<< "EOF"No expansion
<< \EOFNo expansion
<<- EOFStrip leading tabs

echo vs printf vs heredoc

ToolBest for
echoQuick messages
printfFormatted output
heredocMulti-line input

Best Practices

✅ Do This:

# Use printf in scripts for portability
printf "Name: %s\n" "$name"           # ✅

# Always add \n with printf
printf "Hello\n"                      # ✅

# Use printf '%s\n' to print variables safely
printf '%s\n' "$var"                  # ✅

# Quote heredoc delimiter to prevent expansion
cat << 'EOF'                          # ✅
$literal text
EOF

# Use here documents for config generation
cat > config << EOF                  # ✅
host=$HOSTNAME
port=8080
EOF

# Use tabs with <<- for indented heredocs
cat <<- EOF                           # ✅
	indented
	EOF

# Redirect errors to stderr
echo "Error" >&2                      # ✅

# Use echo for simple messages
echo "Done"                           # ✅

❌ Don’t Do This:

# Don't rely on echo -e being portable
echo -e "a\nb"                        # ⚠️  not in dash

# Don't forget \n with printf
printf "Hello"                        # ❌ no newline

# Don't pass a variable as printf format
printf "$var\n"                       # ❌ format injection

# Don't use unquoted delimiter when you want literal text
cat << EOF                            # ⚠️  expands variables
$literal
EOF

# Don't use spaces with <<- (only tabs)
cat <<- EOF                           # ❌ spaces not stripped
    indented
EOF

# Don't forget the closing delimiter
cat << EOF                            # ❌ hangs waiting for EOF
some text

# Don't mix echo styles across shells
# Use printf for portable scripts       # ✅

Common Pitfalls

PitfallProblemSolution
echo -e in dashPrints literal -eUse printf
Missing \n in printfNo newlineAdd \n
Variable as formatFormat injectionUse %s
Heredoc expands varsUnwanted expansionQuote the delimiter
<<- with spacesNot strippedUse tabs only
Unclosed heredocScript hangsAdd closing delimiter
Newline missingOutput runs togetherUse echo or add \n
Binary outputGarbledUse printf with care

Real-World Examples

1. Simple Message

#!/bin/bash
echo "Hello, World!"
[ Hello, World! ]

2. Welcome Message

#!/bin/bash
greeting="Welcome to Shell Scripting"
echo $greeting
[ Welcome to Shell Scripting ]

3. Escape Characters

#!/bin/bash
echo -e "This is a newline:\nAnd this is a tab:\t."
[ This is a newline:
And this is a tab:	. ]

4. No Newline

#!/bin/bash
echo -n "Hello, "
echo "World!"
[ Hello, World! ]

5. Formatted Output

#!/bin/bash
name="Alice"
age=30
printf "Name: %s\nAge: %d\n" "$name" "$age"
[ Name: Alice ]
[ Age: 30 ]

6. Float Precision

#!/bin/bash
number=123.456789
printf "%.2f\n" "$number"
[ 123.46 ]

7. Zero-Padded Numbers

#!/bin/bash
for i in 1 2 3; do
    printf "%03d\n" "$i"
done
[ 001 ]
[ 002 ]
[ 003 ]

8. Aligned Table

#!/bin/bash
printf "%-15s %-10s %5s\n" "NAME" "DEPT" "SCORE"
printf "%-15s %-10s %5d\n" "Alice" "Eng" 95
printf "%-15s %-10s %5d\n" "Bob" "Math" 87
[ NAME            DEPT       SCORE ]
[ Alice           Eng           95 ]
[ Bob             Math          87 ]

9. Multi-Line Heredoc

#!/bin/bash
cat << EOF
This is a here document.
It can span multiple lines.
EOF
[ This is a here document. ]
[ It can span multiple lines. ]

10. Heredoc with Variables

#!/bin/bash
name="Alice"
age=30
cat << EOF
Name: $name
Age: $age
EOF
[ Name: Alice ]
[ Age: 30 ]

11. Literal Heredoc

#!/bin/bash
cat << 'EOF'
Name: $name
Age: $age
EOF
[ Name: $name ]
[ Age: $age ]

12. Generate a Config File

#!/bin/bash
cat > /etc/myapp.conf << EOF
[server]
host = $HOSTNAME
port = 8080

[logging]
level = info
EOF
echo "Config written"
[ Config written ]

13. Generate a Script

#!/bin/bash
cat > backup.sh << 'EOF'
#!/bin/bash
tar -czf /backup/home-$(date +%F).tar.gz /home
EOF
chmod +x backup.sh
echo "Backup script created"
[ Backup script created ]

14. Send an Email

#!/bin/bash
mail -s "Daily Report" admin@example.com << EOF
Report for $(date +%F)

System: $HOSTNAME
Uptime: $(uptime -p)
Disk: $(df -h / | tail -1)
EOF

15. SQL Query

#!/bin/bash
mysql -u root -p << EOF
SELECT COUNT(*) FROM users;
SELECT * FROM orders WHERE total > 100;
EOF

16. Remote Commands over SSH

#!/bin/bash
ssh user@host << EOF
cd /var/log
ls -lh
df -h
EOF

17. Indented Heredoc

#!/bin/bash
if true; then
    cat <<- EOF
	This is indented in the script
	but not in the output.
	EOF
fi
[ This is indented in the script ]
[ but not in the output. ]

18. Progress with printf

#!/bin/bash
for i in $(seq 1 100); do
    printf "\rProgress: %3d%%" "$i"
    sleep 0.01
done
echo
[ Progress: 100% ]

19. Colored Output

#!/bin/bash
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo -e "${RED}Error:${NC} something failed"
echo -e "${GREEN}Success:${NC} all done"
[ Error: something failed ]
[ Success: all done ]

20. Log with Timestamps

#!/bin/bash
log() {
    printf "[%s] %s\n" "$(date +%F' '%T)" "$1"
}
log "Starting backup"
log "Backup complete"
[ [2024-01-15 10:00:00] Starting backup ]
[ [2024-01-15 10:00:00] Backup complete ]

Visual: echo vs printf vs heredoc

┌──────────────────────────────────────────────┐
│                  echo                        │
│                                              │
│  echo "Hello"                                │
│  → [ Hello ]                                 │
│                                              │
│  • Quick, simple                             │
│  • Adds newline automatically                │
│  • Escape handling varies by shell           │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│                 printf                       │
│                                              │
│  printf "Name: %s\n" "Alice"                 │
│  → [ Name: Alice ]                           │
│                                              │
│  • Precise formatting                        │
│  • No automatic newline                      │
│  • Portable — same everywhere                │
│  • Width, precision, types                   │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│               here document                  │
│                                              │
│  cat << EOF                                  │
│  Line 1                                      │
│  Line 2                                      │
│  EOF                                         │
│  → [ Line 1 ]                                │
│  → [ Line 2 ]                                │
│                                              │
│  • Multi-line input                          │
│  • No temp files                             │
│  • Optional variable expansion               │
│                                              │
└──────────────────────────────────────────────┘

Summary

CommandPurposeExample
echo "text"Print textecho "Hello"
echo $VARPrint variableecho $name
echo -nNo newlineecho -n "Hi "
echo -eInterpret escapesecho -e "a\nb"
printf "%s"Formatted stringprintf "%s\n" "$name"
printf "%d"Integerprintf "%d\n" 42
printf "%.2f"Float precisionprintf "%.2f\n" 3.14
printf "%-10s"Left-alignprintf "%-10s|\n" "x"
printf "%05d"Zero-padprintf "%05d\n" 42
cat << EOFHeredoccat << EOF ... EOF
cat << 'EOF'Literal heredocNo variable expansion
cat <<- EOFStrip tabsIndented heredoc
cmd << EOFFeed stdinmysql << EOF ... EOF
cat > file << EOFWrite fileGenerate config

Key takeaways:

  • echo is quick and simple — use it for short messages
  • echo -e interprets escapes but is not portable — use printf in scripts
  • echo -n suppresses the trailing newline
  • printf gives you precise control — width, precision, types
  • printf does not add a newline — include \n yourself
  • Use printf '%s\n' "$var" to safely print variables that may contain %
  • Here documents let you feed multi-line input to any command
  • Use << EOF to allow variable expansion, << 'EOF' to prevent it
  • Use <<- EOF to strip leading tabs (not spaces)
  • Here documents are perfect for generating configs, scripts, SQL, and emails
  • Combine printf with echo and heredocs for full control over output

Remember: echo for quick, printf for precise, heredocs for multi-line. In scripts, prefer printf — it’s portable and predictable. Watch out for echo -e in dash. Always add \n with printf. Quote your heredoc delimiter when you want literal text. And use heredocs whenever you need to feed a block of text to a command — they’re cleaner than echo chains and safer than temp files.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!