zsh-workers
 help / color / mirror / code / Atom feed
* Unexpected Results/Heisenbug
@ 2000-05-11  0:48 Ken Smith
  2000-05-11  4:04 ` Bart Schaefer
  0 siblings, 1 reply; 2+ messages in thread
From: Ken Smith @ 2000-05-11  0:48 UTC (permalink / raw)
  To: zsh-workers

[-- Attachment #1: Type: text/plain, Size: 430 bytes --]

Attached is a script which surprised me when I tested it.  When run with
DEBUG=0, the output is as I would expect.  When run with DEBUG=1, the
for loop sets ${each} to "continue:" after the debugging code executes. 
Is this a problem with my nubile scripting skills or does this signify a
problem with zsh?  Please cc responses to ken at smith.net as I am not a
subscriber of the zsh-workers mailing list.

Thank you for any tips.

[-- Attachment #2: zsh_for_test --]
[-- Type: text/plain, Size: 1355 bytes --]

#!/bin/zsh

DEBUG=1

function debugging
{
	[[ ${+DEBUG} == 1 && ${DEBUG} == 1 ]]
}
function format_output
{
	function arg_len
	{
		echo $1 | wc -c
	}

	heading_separator=": "
	prefix=${1}${heading_separator}
	prefix_len=`arg_len ${prefix}`
	if [[ ${+COLUMNS} == 1 ]]; then
	columns=${COLUMNS}
	else
		columns=80
	fi

	terminal_position=0
	first_argument=1
	just_wrote_prefix=0
	for each in $*; do
		if [[ ${first_argument} == 1 ]]; then
			first_argument=0
			continue
		fi
		if [[ ${terminal_position} == 0 ]]; then
			echo -n ${prefix}
			just_wrote_prefix=1
		fi
		token_len=`arg_len ${each}`
		((terminal_position += token_len + 1))
		if [[ ${terminal_position} > ${columns} ]]; then
			# If we just wrote the prefix, then we
			# have to write this token where it stands
			# even though it will wrap.
			if [[ ${just_wrote_prefix} == 1 ]]; then
				echo ${each}
				just_wrote_prefix=0
				((terminal_position = 0))
			else
				echo -n "\n${prefix}${each} "
				((terminal_position =
					prefix_len + token_len))
				just_wrote_prefix=0
			fi
		else
			echo -n "${each} "
			just_wrote_prefix=0
		fi
	done
	echo
}

if debugging; then
	format_output DEBUG Executing \"${0} ${*}\"
fi

for each in test1 test2 test3; do
	echo "***each=${each}"
	if debugging; then
		format_output DEBUG press enter to continue:
		read
	fi
	echo "***each=${each}"
done

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Unexpected Results/Heisenbug
  2000-05-11  0:48 Unexpected Results/Heisenbug Ken Smith
@ 2000-05-11  4:04 ` Bart Schaefer
  0 siblings, 0 replies; 2+ messages in thread
From: Bart Schaefer @ 2000-05-11  4:04 UTC (permalink / raw)
  To: Ken Smith, zsh-workers

On May 11,  2:48am, Ken Smith wrote:
: Subject: Unexpected Results/Heisenbug
:
: Attached is a script which surprised me when I tested it.  When run with
: DEBUG=0, the output is as I would expect.  When run with DEBUG=1, the
: for loop sets ${each} to "continue:" after the debugging code executes. 
: Is this a problem with my nubile scripting skills or does this signify a
: problem with zsh?

It's a problem with your scripting.

(Whether your skills are "of marriageable condition or age" I'll leave to
others to decide.)

Here's the bug that's confusing you:

: function format_output
: {
[...]
: 	for each in $*; do
[...]
: 	done
: }
[...]
: 
: for each in test1 test2 test3; do
[...]
: done

You've used the same parameter name in both loops.  Since you haven't
declared the one in format_output to be "local", they both refer to the
same global parameter.  ("Variables" in zsh are called "parameters" for
obscure reasons.  The terms are almost always synonymous.)

Other comments:

: function debugging
: {
: 	[[ ${+DEBUG} == 1 && ${DEBUG} == 1 ]]
: }

If the only values are integers, you can write this more succinctly using
the arithmetic syntax:

	(( DEBUG == 1 ))

: function format_output
: {
: 	function arg_len
: 	{
: 		echo $1 | wc -c
: 	}

All functions are of global scope, so declaring this one inside another
only serves to (a) delay its definition until format_output is first run
and (b) needlessly redefine it every time format_output runs again. 

Aside from that, it's completely unnecessary in zsh, because you can get
the length of any scalar (string or integer) parameter by writing $#xxx
or ${#xxx} where xxx is of course the parameter name.  So here:

: 	heading_separator=": "
: 	prefix=${1}${heading_separator}
: 	prefix_len=`arg_len ${prefix}`

	prefix_len=$#prefix

Usually, it's not even worth storing the length in another parameter.

: 	if [[ ${+COLUMNS} == 1 ]]; then
: 	columns=${COLUMNS}
: 	else
: 		columns=80
: 	fi

Fine, but you could avoid the entire if/then/else/fi by writing:

	columns=${COLUMNS:-80}

: 	terminal_position=0
: 	first_argument=1
: 	just_wrote_prefix=0

You should have declared all these parameters (plus "each") as "local",
for example:

	local heading_separator=": "

You could even go so far as to declare some of them to be integers:

    integer terminal_position=0 first_argument=1 just_wrote_prefix=0

Any such declaration within a function scope creates a local parameter,
even if it doesn't use the word "local".  But you have to preceed the
list of parameter names with one of the command words typeset, local,
integer, declare, readonly, or (in 3.1.6+) float; if you simply assign
to the name without such a declaration, you create (or re-use) a global.

: 	for each in $*; do
: 		if [[ ${first_argument} == 1 ]]; then
: 			first_argument=0
: 			continue
: 		fi

You did this to avoid writing out the first argument because it's been
stored in $prefix already.  But you could instead have discarded the
first argument before beginning the loop, by writing:

	shift

After which you don't need $first_argument, and you don't need the test
every time around the loop.

: 		if [[ ${terminal_position} == 0 ]]; then
: 			echo -n ${prefix}
: 			just_wrote_prefix=1
: 		fi
: 		token_len=`arg_len ${each}`

Once again:
		token_len=$#each

: 		((terminal_position += token_len + 1))
: 		if [[ ${terminal_position} > ${columns} ]]; then

Inside [[ ]], the > operator does string comparison, e.g. [[ 2 > 10 ]] is
true.  You probably meant:

		if (( terminal_position > columns )); then

: 			# If we just wrote the prefix, then we
: 			# have to write this token where it stands
: 			# even though it will wrap.
: 			if [[ ${just_wrote_prefix} == 1 ]]; then
: 				echo ${each}
: 				just_wrote_prefix=0
: 				((terminal_position = 0))
: 			else
: 				echo -n "\n${prefix}${each} "
: 				((terminal_position =
: 					prefix_len + token_len))
: 				just_wrote_prefix=0
: 			fi
: 		else
: 			echo -n "${each} "
: 			just_wrote_prefix=0
: 		fi
: 	done
: 	echo

All fine, except that you could have factored out the assignment to
just_wrote_prefix, since you're doing it in every possible branch.

: }
: 
: if debugging; then
: 	format_output DEBUG Executing \"${0} ${*}\"
: fi
: 
: for each in test1 test2 test3; do
: 	echo "***each=${each}"
: 	if debugging; then
: 		format_output DEBUG press enter to continue:
: 		read
: 	fi
: 	echo "***each=${each}"
: done

Also fine, provided you used "local each" in the format_output function.

Hope that helps.

-- 
Bart Schaefer                                 Brass Lantern Enterprises
http://www.well.com/user/barts              http://www.brasslantern.com


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2000-05-11  4:05 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2000-05-11  0:48 Unexpected Results/Heisenbug Ken Smith
2000-05-11  4:04 ` Bart Schaefer

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).