[comp.unix.shell] Help needed with conditional statement for alias in csh

miller@uwovax.uwo.ca (Greg Miller) (09/12/90)

Hi!

I have been having great problems in setting up an alias within csh.

I want to test for the presence of a file, and if it exists then
to execute some statements e.g.


if ( { test -s /usr/spool/mail/miller } ) then; echo "" ; echo "New mail" ;\
 echo "" ; endif


The above does not work because when executed the endif statement is not
reached.  The problem appears to be related to the brackets associated
with the expression in the condition.

Could somebody please tell me what I am doing wrong and how to do
this in a one line command that can be aliased?

Thanks!

  J G Miller                         telephone:  (519) 679-2111 extension 6325
  Room 024, Chemistry Building        InterNet:  <a4346@uwocc1.uwo.CA>
  University of Western Ontario       NetNorth:  <A4346@UWOCC1.BITNET>
  LONDON, Ontario, N6A 5B7            mailpath:  {uunet!watmath,utai}!ria!a4346

-------------------------------------------------------------------------------

miller@uwovax.uwo.ca (Greg Miller) (09/12/90)

In article <6929.26ed0b53@uwovax.uwo.ca>, miller@uwovax.uwo.ca (Greg Miller) writes:
> Hi!
> 
> I have been having great problems in setting up an alias within csh.
> 
> I want to test for the presence of a file, and if it exists then
> to execute some statements e.g.
> 
> 
> if ( { test -s /usr/spool/mail/miller } ) then; echo "" ; echo "New mail" ;\
>  echo "" ; endif
> 

Further to my original post, I have since found that the problem is
not associated with the brackets around the expression but is
related to the execution of test.

If the file is present, the condition is met and the statements are
executed.

If the file is zero length, the condition is not true, then
the sequence of commands are not executed but the if statement
is not terminated.

And ideas on why this bizarre behavior and how to get it to
work properly?

J G Miller

chris@mimsy.umd.edu (Chris Torek) (09/14/90)

In article <6932.26ed237f@uwovax.uwo.ca> miller@uwovax.uwo.ca
(Greg Miller) writes:
>Followup-To: j g miller <miller@ria.ccs.uwo.ca>

(No wonder there have been no followups.  None of `j', `g', `miller', or
`<miller@ria.ccs.uwo.ca>' are valid newsgroup names....)

>if ( { test -s /usr/spool/mail/miller } ) then; echo "" ; echo "New mail" ;\
> echo "" ; endif

>If the file is present, the condition is met and the statements are
>executed.
>
>If the file is zero length, the condition is not true, then
>the sequence of commands are not executed but the if statement
>is not terminated.

Correct.

>Any ideas on why this bizarre behavior

This is the C shell we are talking about.  Do not expect anything
resembling sanity.

>and how to get it to work properly?

Do not use an alias.

The C shell's `parser' looks for a *line* whose first word is `else' or
`endif' after the C shell executes a false `if (expr) then'.  While
scanning for such a line it notes lines that have the form

	if<whitespace>.*<whitespace>then

and increments a `false if' counter.  You can thus stick all sorts of
syntactic trash between a false `if' and its corresponding `endif'; only
when the test in the `if' becomes true will the C shell actually notice
that the trash is indeed invalid.  For instance:

	if (0) then
		if (look, unclosed { and !funny characters then
		endif
		echo this does not get echoed
	endif
	echo but this does

It *is* possible to jam a newline into an alias, but if you do you will
quickly run afoul of other C shell bugs.  For instance:

	% alias t 'if (0) then\
		echo foo\
	endif\
	echo bar'
	% t
	? ? %

Nothing gets echoed.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris

schaefer@ogicse.ogi.edu (Barton E. Schaefer) (09/18/90)

In article <6929.26ed0b53@uwovax.uwo.ca> J G Miller <miller@ria.ccs.uwo.CA> writes:
} 
} I have been having great problems in setting up an alias within csh.
} 
} if ( { test -s /usr/spool/mail/miller } ) then; echo "" ; echo "New mail" ;\
}  echo "" ; endif
} 
} Could somebody please tell me what I am doing wrong and how to do
} this in a one line command that can be aliased?

In article <6932.26ed237f@uwovax.uwo.ca> he goes on:
} In article <6929.26ed0b53@uwovax.uwo.ca>, miller@uwovax.uwo.ca (Greg Miller) writes:
} 
} Further to my original post, I have since found that the problem is
} not associated with the brackets around the expression but is
} related to the execution of test.

Chris Torek has already explained that it is csh brain-damage, not a
problem with "the execution of test", that causes this.

} And ideas on why this bizarre behavior and how to get it to
} work properly?

Don't use any "then", "else" or "endif".  The csh "if" syntax also allows

    if condition simple-command

where simple-command can't be a parenthesized subshell or control
statement (while, foreach) of any kind, and can't contain separators
e.g. `|' or `;' are out.

First problem:  You want three lines of output, but you can't use three
echo commands.  Solution (`%' is the csh prompt):
__________

% setenv NEW_MAIL_MESSAGE '\
New mail\
'
% printenv NEW_MAIL_MESSAGE

New mail

%
__________

So in the alias, you use:

if { test -s /usr/spool/mail/miller } printenv NEW_MAIL_MESSAGE

Note that the ( ) around the { } were redundant.

In general, the way to put conditionals into csh aliases is to use a
series of simple-if statements like the one above.  Here's an example
from my .cshrc.  The backslashes are for readability in this posting
only -- if you actually want to use this, you have to remove the
backslash-newline-tab sequences and make the pp alias into one long line.
Ah, the vagaries of csh ....

# Default pushd to home directory (like cd)
# if there is no top-of-stack to swap with.
alias pp 'set pd=(\!*:q);\
	if ($#pd == 0) set pd=(`dirs` ~);\
	if ($#pd > 1) shift pd;\
	pushd $pd[1] >& /dev/null;\
	if ($#pd > 1) popd +2 >& /dev/null;\
	unset pd;\
	dirs'
-- 
Bart Schaefer						schaefer@cse.ogi.edu

per@erix.ericsson.se (Per Hedeland) (09/19/90)

In article <26553@mimsy.umd.edu> chris@mimsy.umd.edu (Chris Torek) writes:
>In article <6932.26ed237f@uwovax.uwo.ca> miller@uwovax.uwo.ca
>>if ( { test -s /usr/spool/mail/miller } ) then; echo "" ; echo "New mail" ;\
>> echo "" ; endif

>This is the C shell we are talking about.  Do not expect anything
>resembling sanity.

Quite. Following the eternal wisdom of the net, I have learned not only
"if you want to do a non-trivial csh script, use sh" but also "if you want
to do a non-trivial csh command, use sh". Thus, a working one-liner could be:

sh -c 'if test -s /usr/spool/mail/miller; then echo ""; echo "New mail"; echo ""; fi'

However, turning this into an alias with csh's idea of quoting is awkward if
at all possible, and you might just as well make it a script. Actually,
IMHO the best solution is to use the other conditional construct, that
happens to be common to sh and csh, and apparently unbroken in the latter:

test -s /usr/spool/mail/miller && (echo ""; echo "New mail"; echo "")

or as an alias:

alias mailtest 'test -s /usr/spool/mail/miller && (echo ""; echo "New mail"; echo "")'

--Per Hedeland
per@erix.ericsson.se  or
per%erix.ericsson.se@uunet.uu.net  or
...uunet!erix.ericsson.se!per

brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (09/19/90)

In article <1990Sep18.223605.29379@eua.ericsson.se> per@erix.ericsson.se (Per Hedeland) writes:
> sh -c 'if test -s /usr/spool/mail/miller; then echo ""; echo "New mail"; echo ""; fi'
> However, turning this into an alias with csh's idea of quoting is awkward if
> at all possible,

Don't give up! If it's not intuitively obvious to you that

  alias mailtest 'sh -c '\''if test -s /usr/spool/mail/miller; then echo ""; echo "New mail"; echo ""; fi'\'

has the right effect, simply use the makealias and quote aliases
presented below.

---Dan


1. How do I quote a string in csh?
2. How do I double-quote a string in csh?
3. How do I make an alias?
4. How do I get the value of a variable into a command?
5. How do I use a string variable in a sed command?


1. How do I quote a string in csh?

Use the quote alias:

  alias a alias
  a quote "/bin/sed 's/\\!/\\\\\!/g' | /bin/sed 's/'\\\''/'\\\'\\\\\\\'\\\''/g'
	   | /bin/sed 's/^/'\''/' | /bin/sed 's/"\$"/'\''/'"

Make sure the alias is all on one line. All quote does is replace ! by \!,
replace ' by '\'', and place single quotes at the beginning and end of the
text. For example,

  % quote
  BEGIN { printf "'" }     (you type this, followed by control-D)
  'BEGIN { printf "'\''" } '     (this is the correctly quoted version)

quote's output will also work under sh.


2. How do I double-quote a string in csh?

The only interpretation inside a single-quoted string is ! and \!. A
double-quoted string allows (in fact, forces) $ and ` interpretation
as well. Here's the dquote alias:

  a dquote "/bin/sed 's/\\!/\\\\\!/g' | /bin/sed 's/"\""/"\""\\"\"\""/g'
	    | /bin/sed 's/^/"\""/' | /bin/sed 's/"\$"/"\""/'"

If you want to prevent interpretation of a $ inside the double-quoted
string, replace it by "\$"; similarly for `. Of course, it may be
simpler in this case to just use single-quoting and replace $foo by
'"$foo"' when you do want it interpreted.


3. How do I make an alias?

Say you've just typed an amazing command that you want to save as an
alias. Use makealias:

  a makealias "quote | /bin/sed 's/^/alias \!:1 /' \!:2*"

For example,

  % foobie 'bletch b' oing     (wow, amazing!)
  % makealias fb
  foobie 'bletch b' oing     (you retype the command, followed by control-D)
  alias fb 'foobie '\''bletch b'\'' oing'

Redirect makealias's output to a file (makealias foo > /tmp/test) and
later copy it to .login, or redirect directly to .login (makealias foo
>> /tmp/.login). (Use .cshrc if you want your aliases in subshells.)

If you want an alias to take arguments, replace a fixed string in the
alias definition by \!:1 (first argument), \!:2* (second argument
through end), etc. There are many more ! substitutions listed in the
csh manual.


4. How do I get the value of a variable into a command?

The shells have set but not show. Here's several ways to print the value
of a variable, $ans, on stdout. ``Working echo'' means one whose worst
vice is a -n option meaning suppress newlines; if your echo interprets
escape sequences, it doesn't qualify. ``Amazing echo'' means one that
doesn't interpret its arguments at all.

sh, with printenv: export ans; printenv ans
sh, with a working echo: (echo -n "$ans"; echo '')
sh, with an amazing echo: echo "$ans"
csh, with a working /bin/echo: (/bin/echo -n "$ans"; /bin/echo '')
csh, with an amazing /bin/echo: /bin/echo "$ans"
csh, with a working builtin echo: echo "$ans"
     NOTE: csh parses builtins strangely, so this works even if ans is "-n...".
sh, on any machine: sed "+$ans" 2>&1 | sed 's/^Unrecognized command: +//'
     CAVEAT: Does not work if $ans contains newlines.
csh, on any machine: sed "+$ans" |& sed 's/^Unrecognized command: +//'
     ANTI-CAVEAT: Because csh is csh, this one works if $ans contains newlines.

The last two hacks are a useful trick to get that variable into the
output stream, which is difficult if both echo and printenv are screwed.
+ can be any character upon which sed chokes. Other similar replacements
include using ls imaginatively and then stripping off the `file not found',
etc.


5. How do I use a string variable in a sed command?

Say you want to replace all occurrences of the string $ans by XXX.
sed "s/$ans/XXX/g" won't work if $ans contains interesting characters.
You have to munge $ans into $pattern so that sed "s/$pattern/XXX/g"
will act as if it had a literal $ans in the first position.

Under sh with printenv,

  export ans ; pattern="`printenv ans | sed 's-\([\.\*\[\\\^\$\/]\)-\\\\\1-g'`"

will do the job. In other cases, rewrite this along the lines of #4 above.
For example, under csh with a working builtin echo, try

  alias literal 'sed '\''s-\([\.\*\[\\\^\$\/]\)-\\\1-g'\'
  alias show 'echo "$\!:1"'

The acid test, of course, is

  show ans | sed "s/`show ans | literal`/XXX/g"

which, no matter what $ans is, will output XXX.


---Dan

dkeisen@Gang-of-Four.Stanford.EDU (Dave Eisen) (09/20/90)

In article <1990Sep18.223605.29379@eua.ericsson.se> per@erix.ericsson.se (Per Hedeland) writes:
>IMHO the best solution is to use the other conditional construct, that
>happens to be common to sh and csh, and apparently unbroken in the latter:
>
>test -s /usr/spool/mail/miller && (echo ""; echo "New mail"; echo "")
>

Speak for your own C-shell. 

In SCO Xenix, the C-shell thinks it really is C:

	test -s /usr/spool/mail/miller 

returns 0 (assuming the file is nonempty) so the C-shell doesn't 
evaluate the second half of the conditional expression.

&& and || both work exactly the opposite of how one would expect them to 
work (and of how they do in fact work in the Bourne shell).



--
Dave Eisen                      	    Home: (415) 323-9757
dkeisen@Gang-of-Four.Stanford.EDU           Office: (415) 967-5644
1447 N. Shoreline Blvd.
Mountain View, CA 94043

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (09/21/90)

In article <1990Sep20.090627.20515@Neon.Stanford.EDU> dkeisen@Gang-of-Four.Stanford.EDU (Dave Eisen) writes:
: In article <1990Sep18.223605.29379@eua.ericsson.se> per@erix.ericsson.se (Per Hedeland) writes:
: >IMHO the best solution is to use the other conditional construct, that
: >happens to be common to sh and csh, and apparently unbroken in the latter:
: >
: >test -s /usr/spool/mail/miller && (echo ""; echo "New mail"; echo "")
: >
: 
: Speak for your own C-shell. 
: 
: In SCO Xenix, the C-shell thinks it really is C:
: 
: 	test -s /usr/spool/mail/miller 
: 
: returns 0 (assuming the file is nonempty) so the C-shell doesn't 
: evaluate the second half of the conditional expression.
: 
: && and || both work exactly the opposite of how one would expect them to 
: work (and of how they do in fact work in the Bourne shell).

So use a binary editor, go in and swap the || and &&.  Or rm /bin/csh.

Such malprogramming should not go unpunished.

Larry Wall
lwall@jpl-devvax.jpl.nasa.gov

per@erix.ericsson.se (Per Hedeland) (09/21/90)

In article <1990Sep20.090627.20515@Neon.Stanford.EDU>,
dkeisen@Gang-of-Four.Stanford.EDU (Dave Eisen) writes:
|> Speak for your own C-shell. 

I spoke for *the* C-shell, as found in the BSD distribution (I wouldn't
dream of claiming any ownership:-). If someone distributes a shell where the
meaning of && and || is reversed, it certainly isn't the C-shell, just as a
compiler for a language that reverses the meaning of those two isn't a C
compiler.

|> In SCO Xenix, the C-shell thinks it really is C:
|> 
|> 	test -s /usr/spool/mail/miller 
|> 
|> returns 0 (assuming the file is nonempty) so the C-shell doesn't 
|> evaluate the second half of the conditional expression.

I don't buy the "really is C" "justification" - I think that the proper
point of view is that in C, as in Bourne shell and C-shell, the expression
following && is evaluated if the preceding expression is *true* - the fact
that C (and the C-shell in 'if' etc) happens to define "true" as non-zero
and the shells and utilities define it as (return code) zero may be
confusing but is nonetheless irrelevant.

Of course, the csh man page doesn't talk about "true" in this context, but
rather "fails" and "succeeds" - surely not even SCO thinks that return code
zero from a Unix program is a failure indication?

--Per Hedeland
per@erix.ericsson.se  or
per%erix.ericsson.se@uunet.uu.net  or
...uunet!erix.ericsson.se!per

karl_kleinpaste@cis.ohio-state.edu (09/21/90)

dkeisen@Gang-of-Four.Stanford.EDU writes:
   && and || both work exactly the opposite of how one would expect them to 
   work (and of how they do in fact work in the Bourne shell).

Yet another vendor shipping a positively prehistoric incantation of
csh.

The version of csh which had inverted sense of || and && existed in
the vicinity of 2.8BSD.  This dates it in the vicinity of 1981 or
1982.  It has long been fixed in more recent versions.  However, that
version is by far the easiest thing to port to SysV (until Rel4) since
it lacked any concept of the "new" tty driver, jobs, and a variety of
other things.

All these vendors (SCO, Microsoft, Microport, Everex) shipping ancient
csh should invest a little time in upgrading their sources for csh to
include features in recent versions.  For example, all these versions
lack (as I recall) the eval builtin and the directory stack as well.
(I was at a Waldenbooks the other night and came across a book on SCO
Xenix.  In the chapter [an entire chapter!] describing csh, one of the
first things shown is a set of aliases to simulate the builtin
directory stack of a proper csh.)  There's no excuse for taking this
LCD approach to csh -- yes, csh is pretty grotesque, but the very
least the vendors could do is pick up an old csh that can be ported,
and a new csh with bugs fixed and common features in place, and merge
the two to create something reasonable.

I did exactly such a thing with exactly such a csh (2.8BSD version) 6
years ago.  It isn't tough; it isn't even particularly time-consuming.
The directory stack and the eval builtin code can be inhaled whole.

--karl
once-semi-proud creator
of a SysVRel1-compatible
job-controllified csh
based on 2.8BSD csh
(SIGTSTP emulated on SIGQUIT)

chris@mimsy.umd.edu (Chris Torek) (09/21/90)

[on && and || in csh]

In article <1990Sep21.093645.16163@eua.ericsson.se>
per@erix.ericsson.se (Per Hedeland) writes:
>I spoke for *the* C-shell, as found in the BSD distribution (I wouldn't
>dream of claiming any ownership:-). If someone distributes a shell where the
>meaning of && and || is reversed, it certainly isn't the C-shell ...

Hm, well, there never was `the' C shell, but rather several different
C shells, at least as of the first VAX distribution.  (The sources for
csh included an assembly-language _doprnt function, and clearly this was
different from the version on the PDP-11 2BSD distribution.)

At any rate, there are two major variants of the C shell, one lacking
job control.  The one without job control (which was the one in the 3BSD
VAX distribution) also had the bug with && and || being reversed.  No
doubt this is the one that was used as a base for the Xenix C shell.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris

jbw@bucsf.bu.edu (Joseph Wells) (09/23/90)

In <12211@ogicse.ogi.edu> schaefer@ogicse.ogi.edu (Barton E. Schaefer) writes:

   Don't use any "then", "else" or "endif".  The csh "if" syntax also allows

       if condition simple-command

   where simple-command can't be a parenthesized subshell or control
   statement (while, foreach) of any kind, and can't contain separators
   e.g. `|' or `;' are out.

   [stuff deleted]

   In general, the way to put conditionals into csh aliases is to use a
   series of simple-if statements like the one above.

Actually, you can get the complete power of the if-then-else-endif
statement in a csh alias by using the "simple if" in combination with the
|| and && operators.  You can translate this pseudo-code (not csh)
statement:

  if "condition" then "statement1" else "statement2" endif

into this csh statement which will work inside an alias:

  if "condition" set status=1 && "statement2" || "statement1"

or this one:

  if (! "condition") set status=1 && "statement1" || "statement2"

As an example, here is a simple alias for listing the command history in
different ways:

  alias h \
  "  if ('\!*' != '') set status=1 && hi \\
  || if ('\!*' !~ [1-9]*) set status=1 && hi -\!* \\
  || history | grep -i '\!*' | tail"
  alias hi 'history | grep ............ | tail'

If you just type "h", it lists the last ten non-trivial history entries.
If you type "h 3", it lists the last three such entries.  If you type
"h ls", it lists the last 10 history entries that contain the string "ls".

This technique won't work correctly if the "condition" has a side effect
that sets "status" to be non-zero.

Enjoy,

-- 
Joe Wells <jbw@bu.edu>