Bash reverse words in string preserving punctuation character position

88 Views Asked by At

Before posting went thru almost all suggestions in stackoverflow wizard but non of them were for bash most in c#. The exercise is for c language.

The problem.

Write a program that reverses the words in a sentence:

Enter a sentence: you can cage a swallow can't you?
Reversal of sentence: you can't swallow a cage can you?

Use a loop to read the characters one by one and store them in a one-dimensional char array. Have the loop stop at a period, question mark, or exclamation point (the "terminating character"), which is saved in a separate char variable. Then use a second loop to search backward through the array for the beginning of the last word. Print the last word, then search backward for the next-to-last word. Repeat until the beginning of the array is reached. Finally, print the terminating character.

I tried using tac with tr to swap spaces with newlines for tac to read but that didnt work for me. Eventually solved it with while loops. Would like to know if there are shorter ways to implement same thing but without awk or sed.

my code:

#!/bin/bash
#set -x

read -rp "Enter a sentence: " -a sentence

if [ -z "$sentence" ]; then
echo "empty..exiting"
exit 1
fi

terminator=""

i=$((${#sentence[@]}-1))
while [ "$i" -ge 0 ]; do
   b=0
   while [ "$b" -lt "${#sentence[$i]}" ]; do
   if [ "${sentence[$i]:$b:1}" == "?" ] || \
   [ "${sentence[$i]:$b:1}" == "!" ] || \
   [ "${sentence[$i]:$b:1}" == "." ]; then
   terminator="${sentence[$i]:$b:1}"
   sentence["$i"]=$(tr -d [:punct:] <<<"${sentence[$i]}")
   break
   fi
   ((b++))
   done
echo -n "$(tr [A-Z] [a-z] <<<${sentence[$i]})"
if [ "$i" -gt 0 ]; then
echo -n " "
fi
((i--))
done

if [ -n "$terminator" ]; then
echo "$terminator"
else
echo
fi

exit 0


Input:

Enter a sentence: Every living thing is a masterpiece, written by nature and edited by evolution!

Output:

evolution by edited and nature by written masterpiece, a is thing living every!

2

There are 2 best solutions below

7
KamilCuk On

I tried using tac with tr to swap spaces with newlines for tac to read but that didnt work for me

if there are shorter ways to implement same thing but without awk or sed.

$ sentence="you can cage a swallow can't you?"
$ echo "$(<<<${sentence%%[.\!?]} tr ' ' '\n' | tac | paste -sd' ')${sentence##*[^.\!?]}"
you can't swallow a cage can you?
  • $(...) command substitution
  • <<< here string, for tr input
  • ${sentence##[.\!?]} - remove . ! or ? from the end of the string
  • tr ' ' '\n' - replaces spaces by newlines
    • <<<$var tr ' ' '\n' or tr ' ' '\n' <<<$var is the same as echo "$var" | tr ' ' '\n'
  • tac - see tac --help
  • paste -sd ' ' - join lines with spaces
  • ${sentence##*[^.\!?]} - remove all characters from sentence up until . ! ?
  • this is not a case of useless use of echo (echo "$(cmd)" is just cmd), becuase it's echo "$(...)${sentence...}"
2
markp-fuso On

Abiding by the instructions in OP's question:

  • Use a loop to read the characters one by one and store them in a one-dimensional char array.
  • Have the loop stop at a period, question mark, or exclamation point (the "terminating character").
  • use a second loop to search backward through the array for the beginning of the last word
  • assumption: beginning of the word is based on finding a space or tab in the character array
  • assumption: we can hardcode the word separator as a space
  • assumption: input sentence is to be converted to all lowercase (not described in the instructions but is performed in OP's current code)
  • if there is no terminator then reverse the entire line of input

One literal interpretation of the instructions:

unset      sentence char_array word
typeset -l sentence                                    # automatically lowercase everything stored in variable $sentence

read -rp "Enter a sentence: " sentence

if [ -z "${sentence}" ]; then
    echo "empty..exiting"
    exit 1
fi

char_array=()                                          # init character array

while IFS= read -r -n1 char                            # read one character at a time
do
    if [[ "${char}" == "." || "${char}" == "?" || "${char}" == "!" ]]
    then
        terminator="${char}"                           # save terminator and ...
        break                                          # break out of loop
    else
        char_array+=( "${char}" )                      # else store character
    fi
done <<< "${sentence}"

#########################

n="${#char_array[@]}"

unset sep                                              # init word separator; 1st time is blank

printf "Reversal of sentence:"                         # print output header

for ((i=(n-1); i>=0; i--))
do
    char="${char_array[$i]}"

    if [[ -z "${char}" || "${char}" == " " || "${char}" == "\t" ]]
    then
        printf "%s%s" "${sep}" "${word}"               # print separator and word
        sep=" "                                        # rest of the time we use space as word separator
        unset word                                     # re-init word
    else
        word="${char}${word}"                          # else append to word
    fi
done

if [[ -n "${word}" ]]                                  # did we exit loop with a word that still needs to be printed?
then
    printf "%s%s%s\n" "${sep}" "${word}" "${terminator}"
else
    printf "%s\n" "${terminator}"
fi

Taking for a test drive:

Enter a sentence: you can cage a swallow can't you?
Reversal of sentence: you can't swallow a cage can you?

Enter a sentence: Every living thing is a masterpiece, written by nature and edited by evolution!
Reversal of sentence: evolution by edited and nature by written masterpiece, a is thing living every!

Enter a sentence: How do I? do this
Reversal of sentence: i do how?

Enter a sentence: End of line with comment. #end of line comment
Reversal of sentence: comment with line of end.

Enter a sentence: a b c
Reversal of sentence: c b a