The following explanation is just a representation for what I would like to achieve.

I have two text files: The first text file log1.txt contain the following entries:


The second text file log2.txt contain the following entries:


I would like to read both texts at the same time and generate the following output:

The first color Black is Ugly
The second color Blue is Nice
The third color Brown is cool
The fourth color Copper is pretty
The fifth color Cyan is Ugly
The sixth color Gold is Nice
The seventh color Gray is cool
The eighth color Green is pretty

How can I achieve the previous output using bash or shell? I tried to apply two loops at the same time :for loop" and/orwhile loop` but did not work! For example, I tried this awkward code:

while IFS= read -r line; do
    for ii in $(cat log1.txt); do

echo "The first color "$i "is" $ii

done <log2.txt

I have no idea or clue how to change between "first color", "second color", ….etc

  • Post what you've tried so far, so we can see what tweak it might need ? – steve Sep 7 '18 at 13:15
  • The name of the operation that you are performing on two tables in that script is a " join", for reference. However, what you actually want is not. – JdeBP Sep 7 '18 at 13:42
  • 2
    What happened to seventh? – Stéphane Chazelas Sep 7 '18 at 13:44
  • 1
    Would it be appropriate to ask why you want to do this in bash, rather than simply using a more capable scripting language? – jamesqf Sep 7 '18 at 17:22

With zsh and with libnumbertext-tools's spellout on Debian:

#! /bin/zsh -
adjectives=(${(f)"$(head -n ${#colors} <log2.txt)"})

/usr/lib/libnumbertext/spellout -l /usr/share/libnumbertext/en \
  -p ordinal 1-$#colors |
for color adjective in ${colors:^^adjectives}; do
  read num &&
  print -r The $num color $color is $adjective

(note that it's US-English. For instance, for 101, you get one hundred first instead of one hundred and first)

If you can't install zsh or any software that spells out numbers but have a list of English ordinals in a third log3.txt file, you could do in most shells including bash (Bourne-like, rc-like, fish at least):

#! /bin/sh -
awk '
  BEGIN {while ((getline a < "log2.txt") > 0) adjective[na++] = a}
    if ((getline num < "log3.txt") <= 0) num = NR "th"
    print "The "num" color "$0" is "adjective[(NR-1)%na]
  }' log1.txt

(falling back to <digits>th if we run out of English numbers).

  • unfortunately I don't have zsh on Centos 7 to test it. thank you – user308606 Sep 7 '18 at 13:52
  • 1
    @Kasper, try sudo yum install zsh, or install in your home directory from source. You'd need that spellout utility as well. – Stéphane Chazelas Sep 7 '18 at 13:54
  • No creative use of paste and a multiply-repeated log2.txt? (-: – JdeBP Sep 7 '18 at 14:11
  • @JdeBP, the multiply-repeated would get ugly anyway. – Stéphane Chazelas Sep 7 '18 at 14:15

Your shell does not know English, so automatically generating the spelled-out numbers with correct suffixes to an arbitrary count would involve some additional effort. With just digits for the numbering and the additional presumption that log1.txt is the longer file, try this:

log1_length=$(wc -l <log1.txt)
log2_length=$(wc -l <log2.txt)

for i in $(seq $log1_length); do
    arg1=$(head -$i <log1.txt | tail -1)
    arg2=$(head -$(((i-1) % log2_length + 1)) <log2.txt | tail -1)
    echo "Color No. $i $arg1 is $arg2."

You can achieve what you want using case control structure as follows:

log1_length=$(wc -l <log1.txt)
log2_length=$(wc -l <log2.txt)

for i in $(seq $log1_length); do 
    arg1="$(head -$i <log1.txt | tail -1)"
    arg2="$(head -$(((i-1) % log2_length + 1)) <log2.txt | tail -1)"
   # Case control structure to replace digit equivalent in words 
    case ${i} in
        1) echo -n "The first color ";;
        2) echo -n "The second color ";;
        3) echo -n "The third color ";;
        4) echo -n "The fourth color ";;
        5) echo -n "The fifth color ";;
        6) echo -n "The sixth color ";;
        7) echo -n "The seventh color ";;
        8) echo -n "The eighth color ";;
        9) echo -n "The ninth color ";;
       10) echo -n "The tenth color ";;
       11) echo -n "The eleventh color ";;
    echo ${i}"$i${arg1} is ${arg2}" |  tr -d '0123456789'   

The output is as follows:

The first color Black is Ugly
The second color Blue is Nice
The third color Brown is cool
The fourth color Copper is pretty
The fifth color Cyan is Ugly
The sixth color Gold is Nice
The seventh color Gray is cool
The eighth color Green is pretty
  • Why echo the digits and then delete them again? – Sam Sep 7 '18 at 14:58
  • Why the digit=$(echo $i)? and echo $digit | tr -d '0123456789'? – Stéphane Chazelas Sep 7 '18 at 14:59
  • @Sam, if you echo $digit the output is something like The first color 1 . Given that the OP would like to see The first color black is ugly and to get around that just trim the numbers out – user88036 Sep 7 '18 at 15:00
  • 1
    Note that it runs 8 commands for each line of log1. Using those head+tail for each line means the files have to be read from the start at each iteration which is going to be even less efficient than using while read loops – Stéphane Chazelas Sep 7 '18 at 15:01
  • 1
    So why outputting that digit in the first place (twice with $digit$i) just to remove it later with tr (which would also remove digits in color names or adjectives). Also why digit=$(echo $i) and not digit=$i (or for digit instead of for i). – Stéphane Chazelas Sep 7 '18 at 15:04

I am surprised nobody has suggested using arrays. Here is my crude attempt (using the log3.txt idea from @Stephane above.


nl1=$( wc -l < log1.txt )
nl2=$( wc -l < log2.txt )
nlnums=$( wc -l < nums.txt )

declare -a arr1[$nl1]
declare -a arr2[$nl2]
declare -a nums[$nlnums]

for (( i=0; i < $nl1; i++ ))
    read arr1[$i]
done < log1.txt

for (( i=0; i < $nl2; i++ ))
    read arr2[$i]
done < log2.txt

for (( i=0; i < $nlnums; i++ ))
    read nums[$i]
done < nums.txt

for (( i=0; i < $nl1; i++ ))
    echo "The ${nums[$i]} color ${arr1[$i]} is ${arr2[$j]}"
    j=$(( (j+1) % $nl2 ))

The file nums.txt is as follows:


The code needs to be cleaned a bit but illustrates the point.

  • Mr. Downvoter, can you please explain why you downvoted? – unxnut Sep 8 '18 at 1:43

Your Answer

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