[net.bugs.4bsd] C-Shell weirdness

nose@nbires.UUCP (Steve Dunn) (03/25/86)

I've been foolish enough to try to write a shell script lately. I'm 
using the C-shell on 4.2 berkley UNIX. Since both UNIX and its 
documentation are for me just a bit cryptic (Ahem) I can't even be 
sure what's a bug and what's actually really intended to be that way, so
I'm posting this to both bugs and wizards. I am interested in anyone who
has solutions to or explanations for any of these problems.

1. if tests:

   the statement:

           if ($arg == '-b') echo 'UNIX is fun'

Will get the error "If: missing filename" if $arg happens to equal
-x or -e or any of the other constructs used to test file attributes.
The statement will NOT fail if $arg happens to equal other reserved
words such as "if" or "foreach". I did find a kludge to get
around this but it makes be want to rip out my intestines with a fork.
(Well actually its not quite *that* bad...) The statement can be replaced
by this one:

	 if (x$arg == 'x-b') echo 'UNIX is fun'

Is there a better way to do this and why does this happen in the first
place?

2: Count of number of words in a variable (Or when is nothing something)

This:

set hosed = ''
echo $#hosed

yields the result '1'

It seems to me that a variable with null contents has ZERO words not one

3: Logical not operator

According to the csh man page the logical not operator "!" is available.
Unfortunately the statements (in a shell script)

set lights_on = 1
if (!$lights_on) echo 'nobody home'

yield the message "0: event not found". Apparently the shell is trying
to do a history substitution instead of a negation. I have tried all 
manner of quoting to get around this - nothing works. Well then, given that
"!" is used both to signal history substitutions and as a logical not
operator, how can you tell which the shell will choose to do in a given
situation?

                -Steve "If I only had a brain..." Dunn

woods@hao.UUCP (Greg Woods) (03/26/86)

>    the statement:
> 
>            if ($arg == '-b') echo 'UNIX is fun'

   ...should in fact be:

	     if ("$arg" == "-b") echo 'UNIX is fun'

   This really is a feature, not a bug, and I actually have some shell
scripts that use it to test various kinds of access to files for various
reasons, such as the following trivial example:

foreach access (e r w x)
if (-$access "$file") echo $access access is permitted to $file
end

Moral of story: ALL strings used in if tests should be quoted. Double quotes
still permits variable substitution, single quotes inhibit it (believe it
or not, this IS actually documented in the csh(1) man page! :-)

> 2: Count of number of words in a variable (Or when is nothing something)
> 
> This:
> 
> set hosed = ''
> echo $#hosed
> 
> yields the result '1'

  But of course! The variable has one value: a null string. And, if you do

set hosed=( '' junk)

then what do you suppose $#hosed is? If you got 2, drink a potion of raise
level and read on! :-) If you do

set hosed
echo $#hosed

THEN you will get 0. The difference? A null string is different from no value
at all. This too has it's uses, such as when each word of a variable stands
for an attribute of the object represented by the variable, and a null string
means that attribute does not exist (i.e. a kludgy implentation of a
structure in a shell script. And yes, I'm masochistic; I actually have
real scripts that use this too :-)

> 3: Logical not operator
> 
> According to the csh man page the logical not operator "!" is available.

  Also according to the man page, any non-space character after ! invokes
the history mechanism. While it does not (and probably should) say this
explicitly in the documentation for the ! not operator, it follows
from this that what is needed is a space after the ! when using it as
a unary logical operator, e.g. 

> set lights_on = 1
> if (!$lights_on) echo 'nobody home'

set lights_on=1
if (! $lights_on) echo 'nobody home'

The best way to implement Boolean flags under csh is much simpler: the variable
exists or it doesn't. The construct $?var returns 1 if var is defined and 0
if it isn't, and is NOT an error if var is undefined. I.e. your above example
converts to:

set lights_on
if (! $?lights_on) echo 'nobody home'

Hope this helps.

--Greg

nose@nbires.UUCP (Steve Dunn) (03/26/86)

Greg Woods writes in response to my article on c-shell weirdness:

> 
> > 2: Count of number of words in a variable (Or when is nothing something)
> > 
> > This:
> > 
> > set hosed = ''
> > echo $#hosed
> > 
> > yields the result '1'
> 
>   But of course! The variable has one value: a null string. And, if you do
> 
> set hosed=( '' junk)
> 
> then what do you suppose $#hosed is? If you got 2, drink a potion of raise
> level and read on! :-) If you do
> 
> set hosed
> echo $#hosed
> 
> THEN you will get 0. The difference? A null string is different from no value
> at all. This too has it's uses, such as when each word of a variable stands
> for an attribute of the object represented by the variable, and a null string
> means that attribute does not exist (i.e. a kludgy implentation of a
> structure in a shell script. And yes, I'm masochistic; I actually have
> real scripts that use this too :-)
> 

Sorry, I tried the example that is supposed to yield zero and I got one
instead. Furthermore, according to the manual page for csh under the set
command

         "The second form [set name] sets name to the null string"

The answer is that you can't have a variable with 0 words at all.
This fact I find counter-intuitive and undocumented
----

The answers Greg gave to my questions on if tests and the not operator 
were correct and very helpful although I still can't figure out where
the documentation says that if a variable name in an if test expands to
one of -r, -w, -x etc, the shell will interpet the expanded variable name
as a command.

                 -Steve "Wrong Way" Dunn

woods@hao.UUCP (Greg Woods) (03/27/86)

> > set hosed
> > echo $#hosed
> > 
> > THEN you will get 0.
> 
> Sorry, I tried the example that is supposed to yield zero and I got one
> instead.
> 
> The answer is that you can't have a variable with 0 words at all.
> This fact I find counter-intuitive and undocumented

    I stand corrected. It seems that $#var is either an error or a positive
integer. Counterintuitive is debatable, however; a variable with no words
is kind of like saying something that is nothing. The only exception
I am aware of to this is that $#argv CAN be zero if it occurs in a shell
which was given no arguments (which includes your login shell; try it).
THIS is what is counter-intutive to ME, that argv behaves differently than
all other variables in this respect.

> I still can't figure out where
> the documentation says that if a variable name in an if test expands to
> one of -r, -w, -x etc, the shell will interpet the expanded variable name
> as a command.

  The C-shell is a VERY complicated program, obviously; it would
be nearly impossible to document how it would behave on every conceivable
possibility. But in any case, it DOES say that parsing and substitutions (which
presumably includes variable substitutions) are performed before the
command is executed.

--Greg

greg@ncr-sd.UUCP (Greg Noel) (03/27/86)

In article <2024@hao.UUCP> woods@hao.UUCP (Greg Woods) writes:
>> > set hosed
>> > echo $#hosed
>> > THEN you will get 0.
>> Sorry, I tried the example that is supposed to yield zero and I got one
>> instead.
>> The answer is that you can't have a variable with 0 words at all.
>> This fact I find counter-intuitive and undocumented
>    I stand corrected. It seems that $#var is either an error or a positive
>integer. Counterintuitive is debatable, however; .....

No, there is a way to get a C shell variable with zero words:
  set hosed = ()
Counterintuitive is relative -- this was obvous to me; but then, I wasn't
trying to do it, it was an accidential side-effect of doing:
  set something = (`some command`)
  if $#something ......
-- 
-- Greg Noel, NCR Rancho Bernardo    Greg@ncr-sd.UUCP or Greg@nosc.ARPA

wyatt@cfa.UUCP (Bill Wyatt) (03/28/86)

> ... If you do
> 
> set hosed
> echo $#hosed
> 
> THEN you will get 0. The difference? A null string is different from no value
> at all. ...

Small problem: on my system (Ultrix 32m 1.1) the above gives 1, not 0.
No, I did an `unset hosed' first to make sure no garbage was left behind.
-- 

Bill    UUCP:  {seismo|ihnp4|cmcl2}!harvard!talcott!cfa!wyatt
Wyatt   ARPA:  wyatt%cfa.UUCP@harvard.HARVARD.EDU

chris@umcp-cs.UUCP (Chris Torek) (03/28/86)

In article <2024@hao.UUCP> woods@hao.UUCP writes:

>It seems that $#var is either an error or a positive integer.

Not so:

	% set foo
	% echo $#foo
	1
	% set foo=()
	% echo $#foo
	0
	%
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1415)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu

jpn@teddy.UUCP (03/28/86)

>> set hosed
>> echo $#hosed
>> 
>> THEN you will get 0.
>
>Small problem: on my system (Ultrix 32m 1.1) the above gives 1, not 0.

Uh, all csh variables (except argv) will always have ONE element unless you
used the set notation when setting their value.  The way to create a variable
with zero elements is:

set hosed = ()

roger@ll-xn.ARPA (Roger Hale) (03/28/86)

In article <196@cfa.UUCP>, wyatt@cfa.UUCP (Bill Wyatt) writes:
> > ... If you do
> > 
> > set hosed
> > echo $#hosed
> > 
> > THEN you will get 0.
> 
> Small problem: on my system (Ultrix 32m 1.1) the above gives 1, not 0.

set foo=()
echo $#foo

gives me 0.  (4.2bsd vax.)  I suspect it will elsewhere too.

Roger Hale	Internet:  roger@ll-xn.arpa

woods@hao.UUCP (Greg Woods) (03/29/86)

> No, there is a way to get a C shell variable with zero words:
>   set hosed = ()
> Counterintuitive is relative -- this was obvous to me; but then, I wasn't
> trying to do it, it was an accidential side-effect of doing:
>   set something = (`some command`)
>   if $#something ......

  Well, I still think Steve Dunn's postings were more whiny than was necessary,
but I'm glad he posted them anyway, because now I've learned something about
a program that I use extensively that I didn't know before.

--Greg

jsdy@hadron.UUCP (03/29/86)

In article <676@nbires.UUCP> nose@nbires.UUCP (Steve Dunn) writes:
>           if ($arg == '-b') echo 'UNIX is fun'
>Will get the error "If: missing filename" if $arg happens to equal
>-x or -e or any of the other constructs used to test file attributes.
>	 if (x$arg == 'x-b') echo 'UNIX is fun'
>Is there a better way to do this and why does this happen in the first
>place?

[The rest of this was ably answered elsewhere.  HOWEVER ...]

Consider what the command line is when $arg is -e or -x or whatever:
	if (-e == '-b') ...
Now, doesn't that just look like it's asking to evaluate -e on file
"==", or something like that?  Of course the parser recognises "=="
as a relation, and so complains instead that there's no file name.

Your alternative is actually a venerable shell-script idiom to cancel
the flag effect of the "-".  However, you really should put $arg in
double-quotes to negate the effect of an in-valid string:
	set arg = "-b -e"
	if (x$arg == '-b')
becomes
	if (x-b -e == '-b')
with who knows what syntax error reports.  I'd use:
	if ("X$arg" == "X-b") ...
-- 

	Joe Yao		hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}

jsdy@hadron.UUCP (03/29/86)

In article <2024@hao.UUCP> woods@hao.UUCP (Greg Woods) writes:
>> Sorry, I tried the example that is supposed to yield zero and I got one
>> instead.
>> The answer is that you can't have a variable with 0 words at all.
>> This fact I find counter-intuitive and undocumented

Try set hosed = ().  This is suggested by woods' own earlier posting.
This is the way to get $#hosed == 0.  Anything else (including just
set hosed) sets the variable to one or more words.

>> I still can't figure out where
>> the documentation says that if a variable name in an if test expands to
>> one of -r, -w, -x etc, the shell will interpet the expanded variable name
>> as a command.

See my earlier posting.  What do you expect csh to do with
	if (-e == -b)
???	[;-)]

BTW, why is it that woods@hao's responses keep getting here before
the questions from nbires?   [rhetorical question, mostly.]
-- 

	Joe Yao		hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}

maartenj@ark.UUCP (03/30/86)

In article <2024@hao.UUCP> woods@hao.UUCP writes:
>> > set hosed
>> > echo $#hosed
>> > 
>> > THEN you will get 0.
>> 
>> Sorry, I tried the example that is supposed to yield zero and I got one
>> instead.
>> 
>> The answer is that you can't have a variable with 0 words at all.
>> This fact I find counter-intuitive and undocumented
>
>    I stand corrected. It seems that $#var is either an error or a positive
>integer. Counterintuitive is debatable, however; a variable with no words
>is kind of like saying something that is nothing. The only exception
>I am aware of to this is that $#argv CAN be zero if it occurs in a shell
>which was given no arguments (which includes your login shell; try it).
>THIS is what is counter-intutive to ME, that argv behaves differently than
>all other variables in this respect.

Try :
	set hosed = ()
	echo $#hosed 
This will give you 0.

The point is the definitions of a `word'. A word is defined as being separated
by a tab, space or newline accept when the string, containing these characters,
is enclosed in `'' or  `"' (with one exception when you do something like:
"`cat /etc/passwd`" this will force new words at newlines).

The actual syntax is of `set' is :
	set arg = ( wordlist )
when you only set the arg to one word you can leave out the `(' and `)':
	set arg = word
Finally you have the abbreviation :
	set arg
This is exactly the same as
	set arg = ''
e.i. it will set arg to a null string.

So $#arg will give the number of words in $arg.
-- 

			Maarten Jan  Huisjes.  (maartenj@vu44.UUCP)
			{seismo|decvax|philabs}!mcvax!vu44!maartenj

greg@ncr-sd.UUCP (Greg Noel) (03/31/86)

In article <713@ark.UUCP> maartenj@vu44.UUCP (Huisjes Maarten Jan) writes:
>
>  ... (a very good technical explaination of how words are parsed) ...
>
>The actual syntax is of `set' is :
>	set arg = ( wordlist )
>when you only set the arg to one word you can leave out the `(' and `)':
>	set arg = word
>Finally you have the abbreviation :
>	set arg
>This is exactly the same as
>	set arg = ''
>e.i. it will set arg to a null string.

Indeed, the theory is that the above is true.  This is a nit, but in actual
practice, the C shell uses different paths to evaluate the different cases.
They are supposed to be equivalent, but sometimes they are not.  I once got
bitten when I did "set path = /special/commands/directory" (case two above).
It turns out that the code that checks when "path" is set and exports into
the PATH environment variable is only present in the flow for the first case
above.  Instead, you have to say "set path = ( /special/commands/directory )"
to get it to work.  I reported this bug and changed it in the C shell I had;
I hope it's been fixed.

I suppose that's a point for evolution by design.........
-- 
-- Greg Noel, NCR Rancho Bernardo    Greg@ncr-sd.UUCP or Greg@nosc.ARPA

lef@nlm-vax.ARPA (Larry Fitzpatrick) (04/02/86)

In article <344@hadron.UUCP> jsdy@hadron.UUCP (Joseph S. D. Yao) writes:

>Your alternative is actually a venerable shell-script idiom to cancel
>the flag effect of the "-".  However, you really should put $arg in
>double-quotes to negate the effect of an in-valid string:
>	set arg = "-b -e"
>	if (x$arg == '-b')
>becomes
>	if (x-b -e == '-b')
>with who knows what syntax error reports.  I'd use:
>	if ("X$arg" == "X-b") ...

if ("$arg" == "-b") echo is sufficient

regards,
fitz
lef@nlm-vax