Using a variable as a for loop expression

I'm writing a script to merge the xkcd webcomic tiles for comic 1110. So far, I have written about 100 lines, and instead of doing each quadrant of the image separately, I've decided to use functions to do this, repeating for every quadrant and using variables for each quadrant to make the function act differently each time. My problem is, I'm having trouble using variables for the for loop expressions, it goes "line 8: ``eval "$a"`': not a valid identifier". This is what I have so far.

PS: I know I'm designated this as PHP, but that's only to get syntax highlighting, since php appears to highlight it the same way.

#!/bin/bash
# Date Created: September 20th, 2012
# Created by Jonathan Bondhus - https://github.com/jbondhus
# Credit to Andreas Reichinger for analysis of image tile placement, xkcd.com for comic. Used ImageMagick code from Antonio Frascarelli.
#
#  This script will merge the files from xkcd comic number 1110, "Click and Drag".

function function_join() {
	echo "#";
	if [[ quadrant -eq 1 ]]; then
	echo "# First Quadrant:"; # Join together the first quadrant
	quadrantName="first"
	function_quadrant
	else
		if [[ quadrant -eq 2 ]]; then
		echo "# Second Quadrant:"; # Join together the second quadrant
		quadrantName="second"
		function_quadrant
		else
			if [[ quadrant -eq 3 ]]; then
			echo "# Third Quadrant:"; # Join together the third quadrant
			quadrantName="third"
			function_quadrant
			else
				if [[ quadrant -eq 4 ]]; then
				echo "# Fourth Quadrant:"; # Join together the fourth quadrant
				quadrantName="fourth"
				function_quadrant
				fi
			fi
		fi
	fi
	echo "#";
	for `eval "$a"`;
	do
	    echo "# row number "$n" ...";
	    convert $b;
	    for `eval "$c"`;
	    do
	        if [ $d ];      # If the tile file doesn't exist, an empty square will be used.
	            then convert $e;
	            else convert $f;
	        fi;
	    done;
	    convert $g;    # Append the image onto the current quadrant
	    rm -f $h;
	    echo "# ... OK";
	    echo "#";
	done;
}

function function_quadrant() {
	if [[ quadrant -eq 1 ]]; then # Set the variables for the first quadrant
	a='((n=$north; n>=1)); n--'
	b='-size 0x1 xc:white resultn$n''w.png'
	c='((w=$west; w>=1; w--))'
	d='-e $n''n$w''w.png'
	e='+append resultn$n''w.png $n''n$w''w.png resultn$n''w.png'
	f='+append resultn$n''w.png _blank.png resultn$n''w.png'
	g='-append $quadrantNameQuadrant.png resultn$n''w.png $quadrantNameQuadrant.png'
	h='resultn$n''w.png'
	else
		if [[ quadrant -eq 2 ]]; then
		a='n=$north; n>=1; n--'
		b='-size 0x1 xc:white resultn$n''e.png'
		c='e=$east; e<=20; e++'
		d='-e $n''n$e''e.png'
		e='+append resultn$n''e.png $n''n$e''e.png resultn$n''e.png'
		f='+append resultn$n''e.png _blank.png resultn$n''e.png'
		g='-append secondQuadrant.png resultn$n''e.png secondQuadrant.png'
		h='resultn$n''e.png'
		else
			if [[ quadrant -eq 3 ]]; then
			a='s=1; s<=$south; s++'
			b='-size 0x1 xc:black results$s''w.png'
			c='w=$west; w>=1; w--'
			d='-e $s''s$w''w.png'
			e='+append results$s''w.png $s''s$w''w.png results$s''w.png'
			f='+append results$s''w.png _black.png results$s''w.png'
			g='-append thirdQuadrant.png results$s''w.png thirdQuadrant.png'
			h='results$s''w.png'
			else
				if [[ quadrant -eq 4 ]]; then
				a='s=1; s<=$south; s++'
				b='-size 0x1 xc:black results$s''e.png'
				c='e=1; e<=20; e++'
				d='-e $s''s$e''e.png'
				e='+append results$s''e.png $s''s$e''e.png results$s''e.png'
				f='+append results$s''e.png _black.png results$s''e.png'
				g='-append fourthQuadrant.png results$s''e.png fourthQuadrant.png'
				h='results$s''e1.png'
				else # If more than 4 quadrants, just to be safe, exit
					exit 1
				fi
			fi
		fi
	fi
}

for (( quadrant = 0; quadrant < 4; quadrant++ )); do
	function_join
done

echo "#################################"
echo "Joining completed!"
echo "#################################"

exit 0

Why it's not working really depends on what $a is.

I'm not sure assembling a big giant command then feeding it into eval is a good idea.

1 Like
((n=$north; n>=1; n--))

is what $a is supposed to be. That way,

for ${a}

actually should be

for ((n=$north; n>=1; n--))

This function has to run multiple times in slightly different ways. Normal commands and command options can easily be written as variables, but I'm not sure whether it's possible to do that for a for loop's expression.

You can't put part of a command inside an eval. It has to be a whole one.

I can't help thinking there must be a simpler way to do this.

How am I supposed to do that, other than evaluating a variable for a for loop expression? Any ideas?

A normal for-loop? I don't see why you've taken this approach at all.

I'm trying to work on an expanded version of this:

for Y in 19n 18n 17n ... 17s 18s 19s
do
        ARGS=""
        for X in 19w 18w 17w ... 17e 18e 19e
        do
                ARGS="$ARGS +append $Y$X.png"
        done

        convert $ARGS $Y.png
done

...but running into memory limitations. I may have to shrink all the images before I begin. Which makes sense since I'd have to shrink them severely to view the entire image at once anyway.

That is some comic :smiley:

Managed to compose a quarter-scale image of it, I think, but it was the .png equivalent of a zip-bomb, a 300K file which would cause things to run out of memory when they opened it. Trying a sixteenth-scale...

Perhaps the posts before has solved your problem?
If not, and if you want to carry on with your solution I think this can get the for-loop to work, it's ugly, but works in bash (FreeBSD):
This

...
    for `eval "$a"`
...
    a='((n=$north; n>=1)); n--'
...

Has to be like - EDIT: I changed this code after advise from @alister in the next post

...
    for (($a_start; $a_expr; $a_eval))
...
    a_start='n=north'
    a_expr='n>=1'
    a_eval='n--'
...

But, @Corona688 has a point here

The command substitution and eval and echo are unnecessary.

    for (($a_start; $a_expr; $a_eval))
...
    a_start='n=north'

Regards,
Alister

1 Like

Thanks, after your explanation it's kind of logical, same as in [[ ... ]] -expressions. I also changed in my last post.

I also noticed when I tested this that there is no need to use $ for a_start etc. (FreeBSD - bash), it seems to expand twice (or more), kind of strange. But I think it would be less readable if you use the "a_"-variables without $ in the for-expression.