Bash script that counts the characters, frequency, and percentage of letters in Spanish sonnets This script will be used to analyze a lot of old Spanish sonnets. I have tested it thoroughly and all seems to be working perfectly. However, I want to make sure I am not missing some detail because I certainly want accurate results. For the math part it does not round up if the last decimal place is above five. I asked the person I am writing it for, and he said that did not matter for his purposes.

My operating system is Debian 9. I am glad to receive any feedback or comments.

Usage:

script sonnet.txt


Sample text: 


  Sancho Panza es aquéste, en cuerpo chico, pero grande en valor,
  ¡milagro extraño! Escudero el más simple y sin engaño que
  tuvo el mundo, os juro y certifico.


Output:

Carácteres          Frecuencia          Porcentaje                                                                                                           
i                   6                   04.87%
e                   17                  13.82%
a                   10                  08.13%
o                   14                  11.38%
u                   7                   05.69%

b                   0                   0
c                   7                   05.69%
d                   3                   02.43%
f                   1                   00.81%
g                   3                   02.43%
h                   2                   01.62%
j                   1                   00.81%
k                   0                   0
l                   5                   04.06%
m                   4                   03.25%
n                   8                   06.50%
p                   4                   03.25%
q                   2                   01.62%
r                   9                   07.31%
s                   8                   06.50%
t                   4                   03.25%
v                   2                   01.62%
w                   0                   0
x                   1                   00.81%
y                   2                   01.62%
z                   1                   00.81%
ñ                   2                   01.62%
Carácteres: 27  Total: 123  Total: 100%


Script:

#!/bin/bash

# Check for argument. 
if [ -z "$1" ]; then
    echo "Script needs an argument."
    exit 1
fi

# Create output file name.
fname=${1%.*}.tsv

# Clear if output file already exists.
echo -n &gt; "$fname"

# Get all alpha characters and cut new line characters.
chars=$(grep -io '[a-z]' "$1" | tr -d '\n')

# Count total alpha characters.
chars_total=$(echo -n "$chars" | wc -m)

# Get all consonants except for ñ.
consonants=$(echo {a..z} | sed 's/[ieaou] //g')

# Set long tab length.
tabs 20

# Echo header.
echo -e "Carácteres\tFrecuencia\tPorcentaje" | tee -a "$fname"

# Get letter name. Count all instances of letters including accented letters. Calculate the percent for each letter. Echo data to table.
for ltr in 'i|í' 'e|é' 'a|á' 'o|ó' 'u|ú|ü' $consonants ñ; do
    ltr_name="${ltr:0:1}"
    ltr_count=$(echo -n "$chars" | grep -Eoi "$ltr" | tr -d '\n' | wc -m)
    ltr_percent=$(echo "scale=4; $ltr_count/$chars_total" | bc | tr -d \. | sed 's/..$/.&amp;%/')
    echo -e "$ltr_name\t$ltr_count\t$ltr_percent"
    if [ "$ltr_name" == "u" ]; then echo | tee -a "$fname"; fi
done | tee -a "$fname"

# Echo footer.
echo -e "Carácteres: 27\tTotal: $chars_total\tTotal: 100%" | tee -a "$fname"
The right tool for the job

Nice script! But Bash is not well-suited for this kind of computation.
Bash is good as glue code,
connecting different applications, bridging the gap,
doing simple stuff.
The task at hand would have been better written in proper programming language, for example Python.

Usability

When the first argument is missing or empty,
the script exits with an error message: "Script needs an argument.".
What kind of argument? It would be better if it gave a hint, for example:

if [ $# = 0 ]; then
    echo "Usage: $0 file"
    exit 1
fi


This gives a hint that the script requires a file argument.

I also changed the condition from -z "$1" to $# = 0.
The former means "first argument is missing or empty",
the latter means "script has no arguments".
This is probably closer to your intention.

Creating empty files

A simple way to create an empty file:

&gt; "$fname"


Don't use flags of echo

The flags of echo are not portable, for example echo -n.
Try to avoid as much as possible.
You can use printf instead.
On the other hand, echo without any flags is fine.

Counting characters

In your input I don't see multibyte characters.
If you don't need to support such characters,
then you could use native Bash syntax ${#chars} to get the length of $chars,
no need for wc -m.

Writing to "$fname"

The script passes some text to | tee -a "$fname" at multiple places:


header
loop output


inside the loop, blank line after the letter u -> this one looks like a bug

footer


Writing | tee ... multiple times is tedious and error-prone.
You could group commands to simplify:

{
    echo header ...
    for ...
    echo footer ...
} | tee -a "$fname"


Probably you can drop the -a from tee to truncate the file,
and that will also allow you to remove the manual truncation at the beginning of the script.