# Friendly Output: Columns and Colors

printf lines output up in columns, with a width for every blank it fills, and tput adds bold and color, but only when \[\[ -t 1 \]\] says the output is going to a terminal, so files and pipes get plain text.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Line up output in columns with printf, using %-22s for left-aligned and %7s for right-aligned fields
- Color a script's output with tput, only when \[\[ -t 1 \]\] says the output is going to a terminal

## Key takeaways

- printf fills the blanks in a pattern: %s is a blank for text, and \n ends the line. printf never adds a new line of its own.
- %-22s fills a blank 22 characters wide, lined up on the left; %7s lines up on the right, which suits numbers. A longer value isn't cut, so pick widths that fit.
- tput bold, tput setaf 2 (green) and tput sgr0 (back to normal) print the codes a terminal reads as styles and colors.
- Color codes in a file or a pipe are junk. [[ -t 1 ]] is true only when the output goes to a terminal, so set the colors only then.

Ten recipes in sentences, such as `Taco Tuesday (25 min, dinner)`, are fine. Forty make your eyes hunt for the minutes in every line. Compare `ls -l` or `df -h`: a header and neat columns, so you read straight down. Many tools add a little color too: `ls` paints folders blue and `grep` highlights what it found. Columns and color are what make a tool pleasant to use every day. Color needs one bit of care, though, and this lesson covers that as well.

## printf: a line with blanks to fill

A form letter has blanks, "Dear ___", filled in for each person. `printf` (read "print f", print formatted) works the same way: a pattern with blanks, then the values to fill them, in order. `%s` (read "a string goes here") is a blank for any text:

```bash
printf '%s takes %s minutes\n' 'Taco Tuesday' 25
```

```text
Taco Tuesday takes 25 minutes
```

Unlike `echo`, `printf` never starts a new line by itself, so the pattern ends with `\n` (read "new line").

## Columns: a width for each blank

Put a number in a blank to give it a width. `%-15s` (read "15 wide, on the left") pads the text with spaces on its right, and `%5s` lines it up on the right. The brackets show where the spaces go:

```bash
printf '[%-15s][%5s]\n' 'Tomato Soup' 20
printf '[%-15s][%5s]\n' 'Slow Cooker Chili' 45
```

```text
[Tomato Soup    ][   20]
[Slow Cooker Chili][   45]
```

Numbers on the right line up their ones under their ones. A value longer than its width isn't cut off; it pushes the rest of the line over, so give each column room for its longest value.

In `recipes`, in `cmd_list`, add a header line right before the `while read` line, and change the `echo` inside the loop to a `printf` with the same widths:

```bash
  printf '%-22s %7s  %s\n' NAME MINUTES CATEGORY
```

```bash
    printf '%-22s %7s  %s\n' "$name" "$minutes" "$category"
```

```bash
./recipes list -c dinner
```

```text
NAME                   MINUTES  CATEGORY
Taco Tuesday                25  dinner
Slow Cooker Chili           45  dinner
```

## Colors with tput

In *A Prompt in Your Colors*, `\e[1;36m` turned on bold cyan. Those **ANSI escape codes** are hard to remember. `tput` (read "t put", terminal put) looks up the right code for your terminal by name: `tput bold`, `tput setaf 2` ("set foreground color 2", green) and `tput sgr0` ("back to normal"). `cat -v` shows the codes it prints, with `^[` for the escape character (the `echo` only ends the line):

```bash
tput bold | cat -v; echo
```

```text
^[[1m
```

Add these lines under `source "$config_file"`, and use them in two places: the header line becomes `printf '%s%-22s %7s  %s%s\n' "$bold" NAME MINUTES CATEGORY "$reset"`, and `cmd_add` says `echo "${green}Added $3.${reset}"`.

```bash
bold=$(tput bold)
green=$(tput setaf 2)
reset=$(tput sgr0)
```

Now the header is bold and "Added" is green on your screen.

![Ubuntu's terminal after ./recipes add 30 dinner "Lemon Garlic Pasta" and ./recipes list -c dinner: the line Added Lemon Garlic Pasta. in green, then the header NAME MINUTES CATEGORY in bold, and three dinners in plain columns below it](https://www.ask-codey.com/lessons/TODO)
 The color numbers are the prompt lesson's, less 30: 1 is red, 2 green, 4 blue.

## Color only on a terminal

Save the list into a file, the way you'd keep a copy, and look inside:

```bash
./recipes list > list.txt
cat -v list.txt | head -n 3
```

```text
^[[1mNAME                   MINUTES  CATEGORY^[(B^[[m
Taco Tuesday                25  dinner
Overnight Oats               5  breakfast
```

The codes went into the file as junk, and they'd confuse a program reading the list through a pipe too. The fix is a question: is the output going to a terminal? `[[ -t 1 ]]` (read "is stream 1 a terminal?") is true only then. Stream 1 is standard output, from *Three Streams: Output, Errors and Input*. Replace the three tput lines with:

```bash
if [[ -t 1 && -z "$NO_COLOR" ]]; then
  bold=$(tput bold)
  green=$(tput setaf 2)
  reset=$(tput sgr0)
else
  bold="" green="" reset=""
fi
```

`NO_COLOR` is a setting some people make to turn color off in every tool that respects it, so yours does too. Make the version `recipes 1.3.0`, and try again:

```bash
./recipes list > list.txt
cat -v list.txt | head -n 3
./recipes list | grep Soup
```

```text
NAME                   MINUTES  CATEGORY
Taco Tuesday                25  dinner
Overnight Oats               5  breakfast
Tomato Soup                 20  lunch
```

Color on the screen, plain text everywhere else: `ls` and `grep` behave exactly this way. `tput` and `[[ -t 1 ]]` work the same on a Mac.

## Check yourself

2 questions to try on the lesson page.

## So what

Columns you can read straight down and color only where a person sees it turn a script's output into something you're glad to look at every day, and still safe to save or pipe.

## Key terms

- [printf](https://www.ask-codey.com/glossary/printf)
- [tput](https://www.ask-codey.com/glossary/tput)
- [ANSI Escape Code](https://www.ask-codey.com/glossary/ansi-escape-code)
- [Standard Streams](https://www.ask-codey.com/glossary/standard-streams)

## Cheat sheets

- [Friendly Output: Columns and Colors](https://www.ask-codey.com/cheat-sheets/bash-friendly-output-cheat-sheet)

## Related

- [A Prompt in Your Colors](https://www.ask-codey.com/lessons/bash-custom-prompt)
- [Where an App Keeps Its Data](https://www.ask-codey.com/lessons/bash-app-data-and-config)
- [Three Streams: Output, Errors and Input](https://www.ask-codey.com/lessons/bash-streams-and-redirection)

Source: https://www.ask-codey.com/lessons/bash-friendly-output
