8

I'm looking for a way to take a text file and put each line one-at-a-time centered on screen with a certain character width.

Sort of like a bare-bones slide show, e.g. seeing the first line until the user presses a key, and then seeing the next line, until all the lines have been viewed.

I suspect there is a basic way to do this in bash, but I haven't found an answer yet.

7
12

Something like that:

#!/usr/bin/env bash

if [ ! "$#" -eq 1 ]
then
    printf "Usage: %s <file>\n" "$0" >&2
    exit 1
fi

file="$1"

display_center(){
    clear
    columns="$(tput cols)"
    lines="$(tput lines)"
    down=$((lines / 2))
    printf '\n%.0s' $(seq 1 $down)
    printf "%*s\n" $(( (${#1} + columns) / 2)) "$1"
}

while IFS= read -r line
do
    display_center "$line"
    read -n 1 -s -r </dev/tty
done < "$file"

Name it centered.sh and use like that:

./centered.sh centered.sh

It will print each line from the given file. Press any key to show the next line. Notice that it's not well tested yet so use with caution and that it'll always print lines starting from the center of the screen so it will make long lines appear more at the bottom.

The first line:

#!/usr/bin/env bash

is a shebang. Additionally, I use env for its features. I tried to avoid Bash and write this script in POSIX shell but I gave up because especially read was very problematic. You should keep in mind that even though it may seem that Bash is ubiquitous it isn't preset everywhere by default, for example on BSD or small embedded systems with Busybox.

In this part:

if [ ! "$#" -eq 1 ]
then
    printf "Usage: %s <file>\n" "$0" >&2
    exit 1
fi

we check if user provided exactly one parameter and if they didn't we print usage info to standard error and return 1, that means an error to a parent process.

Here

file="$1"

we assign filename parameter that user has passed to a variable file that we'll use later.

This is a function that actually prints centered text:

display_center(){
    clear
    columns="$(tput cols)"
    lines="$(tput lines)"
    down=$((lines / 2))
    printf '\n%.0s' $(seq 1 $down)
    printf "%*s\n" $(( (${#1} + columns) / 2)) "$1"
}

There are no function prototypes in Bash so you can't know how many parameters function takes in advance - that one takes only one parameter which is a line to print and it's dereferenced using $1 This functions first clears the screen, then moves down by lines/2 from the top of the screen to reach center of the screen and then it prints centered line using the method I borrowed from here.

That is the loop that reads input file passed by the user and calls display_center() function:

while IFS= read -r line
do
    display_center "$line"
    read -n 1 -s -r </dev/tty
done < "$file"

read is used with -n 1 to read only one character, -s to not echo input coming from a terminal and -r to prevent mangling backslashes. You can learn more about read in help read. We also read from /dev/tty directly because stdin already points to the file - if we didn't tell read to read from /dev/tty the script would very quickly print all lines from the file and exit immediately without waiting for the user to press a key.

2
  • Awesome, couldn't be better. I'll have to review your code to learn more about how you did it. Would you mind posting a few sentences summarizing your approach for someone new to bash? Thanks again for your time!
    – ak0000
    Mar 16 '20 at 17:46
  • 1
    I've added an explanation. Mar 16 '20 at 18:17
5

You can do it with dialog package:

file=lorem                             #Path to the file to be displayed
ln=1                                   #Current line number to be displayed
nlines=$(wc -l "$file"|cut -f1 -d" ")  #Total number of lines of file
while [ "$ln" -le "$nlines" ]; do
    line=$(sed -n "$ln p" "$file")     #sed gets current line
    if dialog --yes-label Previous --no-label Next \
    --default-button no --yesno "$line" 5 100; then
        ln=$((ln-1))
    else
        ln=$((ln+1))
    fi
done

It is a text-based presentation (I took "bare-bones slide show" seriously!), no X session required, that displays one line at a time. You can go backwards or forwards and it ends after the last line.

1
  • I like this answer but I'm going to accept the other one because it has no dependencies and has a more minimal visual. Thanks so much for your time, and for teaching me about the dialog package! Very cool stuff!
    – ak0000
    Mar 16 '20 at 17:44
1

Here's a quick'n'dirty one-liner:

sed ':a;s/^.\{1,77\}$/ &/;ta;s/\( *\)\1/\1/; s/.*/\n\n\n\n\n\n\n\n\n\n\n&\n\n\n\n\n\n\n\n\n\n\n/' < input.txt | more

This assumes an 80x24 terminal window. The sed command centers each line of text, then adds enough leading and trailing newlines to vertically center. The more command allows the user to page through.

1
  • Cool, thanks for the idea!
    – ak0000
    Mar 19 '20 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.