🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Thread View: comp.unix.shell
26 messages
26 total messages Started by CSSE LAN Test Account Wed, 26 Dec 1990 16:19
Finding the last arg
#1997
Author: CSSE LAN Test Account
Date: Wed, 26 Dec 1990 16:19
35 lines
2234 bytes
Well, it should have been easy, but everything I've tried has failed, so
I'll go to the experts... (;-)

What I've been trying to do is write a script whose semantics are sort of
like the cp/mv/ln commands, in that the last argument is special.  If it
is a directory, I want to do something different that amounts to appending
character strings to its name to give file name[s] within the directory;
if it is an ordinary file, I just want to use its name.

The problem is that I can't figure out any Bourne-shell expression that
gives the last argument.  In C-shell, it's easy (argv[$#]).  But I need
to write Bourne shell scripts.  In fact, they need to be BSD bourne shell
scripts rather that ATT Bourne shell scripts.  The difference is probably
significant here, because of the differences in the way these two Bourne
shells handle variables.  Actually, it'd be really nice to find a solution
that is portable, but that may be just a dream.

The obvious thing to try is some sort of expression combining $# with ${},
e.g. ${$#}.  This gets a syntax error.  The simpler $$# is valid, but it
just gives the process id followed by a '#', not even close.  I've also 
tried assigning $# to a variable and using that, but all I've managed to
get is the number of the last arg, not its value.

At the moment I've kludged the job by writing a program lastfld.c that just
prints argv[argc-1].  But it seems incredibly stupid to have to pass all
the args to a subprocess which throws away all but the last one.  I mean,
the shell knows which arg is which; why go to the expense of starting up
a subprocess to learn something that the parent already knows?

The pseudo-code I'd like to use is:
	if [ -d last-command-line-arg ]
	then
		...
	fi
Any ideas?
Re: Finding the last arg
#1998
Author: Rouben Rostamian
Date: Wed, 26 Dec 1990 18:50
22 lines
1565 bytes
In article <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:
 >What I've been trying to do is write a script whose semantics are sort of
 >like the cp/mv/ln commands, in that the last argument is special.  If it
 >is a directory, I want to do something different that amounts to appending
 >character strings to its name to give file name[s] within the directory;
 >if it is an ordinary file, I just want to use its name.
 >
 >The problem is that I can't figure out any Bourne-shell expression that
 >gives the last argument.  In C-shell, it's easy (argv[$#]).  But I need
 >to write Bourne shell scripts.

Here's what you need:
#!/bin/sh
eval lastarg=\${$#}
...etc...


--
Rouben Rostamian                            Telephone: (301) 455-2458
Department of Mathematics and Statistics    e-mail:
University of Maryland Baltimore County     bitnet: rostamian@umbc
Baltimore, MD 21228,  U.S.A.                internet: rostamian@umbc3.umbc.edu
Re: Finding the last arg
#1999
Author: Bob McGowen x4312 dept208
Date: Wed, 26 Dec 1990 22:44
84 lines
3928 bytes
In article <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:
>Well, it should have been easy, but everything I've tried has failed, so
>I'll go to the experts... (;-)
>
>What I've been trying to do is write a script whose semantics are sort of
>like the cp/mv/ln commands, in that the last argument is special.  If it
>is a directory, I want to do something different that amounts to appending
>character strings to its name to give file name[s] within the directory;
>if it is an ordinary file, I just want to use its name.
>
>The problem is that I can't figure out any Bourne-shell expression that
>gives the last argument.  In C-shell, it's easy (argv[$#]).  But I need
>to write Bourne shell scripts.  In fact, they need to be BSD bourne shell
>scripts rather that ATT Bourne shell scripts.  The difference is probably
>significant here, because of the differences in the way these two Bourne

  As an aside, could you explain this comment?  I have had minimal contact
  with BSD, but my experience does not seem to support this statement.

>shells handle variables.  Actually, it'd be really nice to find a solution
>that is portable, but that may be just a dream.
---deleted discussion---
>Any ideas?

Three solutions came to mind:  1) using eval; 2) using shift; 3) using a
combined while loop/if statement with the shift.

   1)  eval echo this is the last argument \$$#

       This works because the shell evaluates the right $ with the # to
       give the last argument (not really, but see later), then re-reads
       the line with the value of this number following the first $, which
       it now sees as an unprotected character and gives the substituted arg
       that matches.  Now for the "not really":  this will only work for
       a list of args that is not bigger than nine.  The tenth arg would
       result in output like:

		this is the last argument a0

       where the a is arg 1 ($10 is the concatenation of $1 and 0).

   2)  shift `expr $# - 1`
       echo the last arg is $1

       The Bourne shell on AT&T's SysV/386 and the Sun OS support a
       numeric argument to the shift.  This number of args are shifted
       out of the list.  So if there are ten args, expr gives nine for
       the shift, the last arg becomes $1.  The problem here is that
       all the preceding args are thrown away.

   3)  while [ ! -z "$1" ]
       do
	  if [ $# -eq 1 ]
	  then
	     last=$1
	     shift
	  else
	     rest="$rest $1"
	     shift
	  fi
       done

       Each argument is collected as encountered on the command line
       (except for the case where it contains white space -- "a b";
       this is a little hard to handle with this type of structure
       because the "arguments" are parsed three times: once as $1,
       again in the if to construct the $rest and later whenever $rest
       is used).  All except the last are collected into the variable
       rest, the last is in the variable last.  You can now manipulate
       the parts as needed.  For instance, to create a path name
       with the last arg as the first component:

       for item in $rest
       do
	  echo $last/$item
       done

I hope this slightly lengthy discussion helps.


Bob McGowan  (standard disclaimer, these are my own ...)
Product Support, Wyse Technology, San Jose, CA
..!uunet!wyse!bob
bob@wyse.com
Re: Finding the last arg
#2009
Author: Jeff Beadles
Date: Thu, 27 Dec 1990 06:09
40 lines
1453 bytes
In <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com writes:


...
>The problem is that I can't figure out any Bourne-shell expression that
>gives the last argument.
...
>Any ideas?


Well, there's nothing that I am aware of in /bin/sh that will ALWAYS allow
this to work.  The problem is that if you have more than 9 arguements, you
have to shift them off to get to what you want.  Here's one try that might
be of use:


#!/bin/sh

if [ $# -gt 0 -a $# -lt 10 ] ; then
	LAST=`eval echo \\$$#`		## 1-9 == Great!
elif [ $# -le 0 ] ; then
	LAST="NO_ARGS"			## 0 == Oops! None!
else
	LAST="TOO_MANY_ARGS"		## >9 == Oops!  Too many!
fi

echo "LAST= >$LAST<"
exit 0

However, this is not all that swift.  "foo a b c d e f g h i j k" won't work
with this.

The only other hope is to save off the arguements and loop until the end.  This
has the possibility of screwing up any special characters and/or white space
though, and is generally not-so-hot.


	-Jeff
-- 
Jeff Beadles		jeff@onion.pdx.com
Re: Finding the last arg
#2002
Author: Conor P. Cahill
Date: Thu, 27 Dec 1990 15:49
11 lines
970 bytes
In article <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:
>The obvious thing to try is some sort of expression combining $# with ${},
>e.g. ${$#}.  This gets a syntax error.  The simpler $$# is valid, but it

Try:
	eval \$$#

-- 
Conor P. Cahill            (703)430-9247        Virtual Technologies, Inc.,
uunet!virtech!cpcahil                           46030 Manekin Plaza, Suite 160
                                                Sterling, VA 22170 
Re: Finding the last arg
#2005
Author: Maarten Litmaath
Date: Thu, 27 Dec 1990 22:15
34 lines
1163 bytes
# How to get the last argument of a shell script reliably.
# If you are sure the number of arguments is in [1-9], you can use this:
#
#	eval last=\$$#
#
# The general (backquote) solution works for ANY number of arguments.
# In POSIX-compatible shells this works for any $#:
#
#	eval last=\${$#}

set a b c d e f g h i j k l m -n	# For example.

case $# in
0)
	# So the user tried to be funny; let's give him a little surprise.
	exec kill -SYS $$
	;;
[1-9])
	eval last=\$$#
	;;
*)
	last=`
		n=$#
		set '' ${1+"$@"}
		shift $n
		echo -n "$1"
	`
esac

echo "last=|$last|"
--
  _ d         _\  _\      2
 ih -- Y = (c A . p  + m c B) Y
    dt                  0
resetting the arguments (was: Re: Finding the last arg)
#2007
Author: Maarten Litmaath
Date: Sat, 29 Dec 1990 00:51
55 lines
1488 bytes
In article <3074@wyse.wyse.com>,
	bob@wyse.wyse.com (Bob McGowen x4312 dept208) writes:
)...
)   3)  while [ ! -z "$1" ]
)       do
)	  if [ $# -eq 1 ]
)	  then
)	     last=$1
)	     shift
)	  else
)	     rest="$rest $1"
)	     shift
)	  fi
)       done

How about the following alternative?  It works for `funny' arguments too.

--------------------cut here--------------------
#!/bin/sh
# find the last argument AND reset all arguments but the one found,
# using only BUILTIN constructs

case $# in
0)
	echo 'No arguments specified.' >&2
	exit 1
esac

argv=
last=

while :
do
	case $# in
	1)
		last=$1
		break
	esac
	eval i$#=\$1
	argv="$argv \"\$i$#\""
	shift
done

eval set x $argv
shift

echo "last=$last"
echo 'new arguments:'
for i
do
	echo "	$i"
done
--
nlp@berlin.mt.cs.cmu.edu: "I heard that an Awk was spotted in Sherwood forest."
rmk@frog.UUCP (Rick Kelly): "It was seen running in the background."
Re: Finding the last arg
#2010
Author: Wm E. Davidsen Jr
Date: Mon, 31 Dec 1990 04:16
20 lines
1518 bytes
In article <1990Dec27.154917.14751@virtech.uucp> cpcahil@virtech.UUCP (Conor P. Cahill) writes:
| In article <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:
| >The obvious thing to try is some sort of expression combining $# with ${},
| >e.g. ${$#}.  This gets a syntax error.  The simpler $$# is valid, but it
| 
| Try:
| 	eval \$$#

  With sh there is no valid way to do this, it breaks when $# > 9. With
ksh the followinf is true:
  eval echo $# \$##		# uses only one digit of $#
  eval echo $# \${$#}		# works for all values

  Hope that clarifies it. I treid several shells, all flavors of sh seem
to stop at $#>9 and need evaluation via shift.
-- 
bill davidsen - davidsen@sixhub.uucp (uunet!crdgw1!sixhub!davidsen)
    sysop *IX BBS and Public Access UNIX
    moderator of comp.binaries.ibm.pc and 80386 mailing list
"Stupidity, like virtue, is its own reward" -me
Re: Finding the last arg
#2011
Author: Lawrence V. Cipriani
Date: Tue, 01 Jan 1991 15:57
18 lines
938 bytes
In article <18476@shlump.nac.dec.com>, lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:

> The pseudo-code I'd like to use is:
> 	if [ -d last-command-line-arg ]
> 	then
> 		...
> 	fi

If you don't need to have the last command line argument in a variable you
can do this:

	if eval [ -d "\${${#}}" ]
	then
		...
	fi
-- 
Larry Cipriani, att!cbvox!lvc or lvc@cbvox.att.com
"I just love the smell of gunpowder!" - Bugs Bunny
Re: Finding the last arg
#2020
Author: Dan Mercer
Date: Tue, 01 Jan 1991 23:42
42 lines
2081 bytes
In article <1990Dec27.154917.14751@virtech.uucp> cpcahil@virtech.UUCP (Conor P. Cahill) writes:
:In article <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com (CSSE LAN Test Account) writes:
:>The obvious thing to try is some sort of expression combining $# with ${},
:>e.g. ${$#}.  This gets a syntax error.  The simpler $$# is valid, but it
:
:Try:
:	eval \$$#
:
:-- 
:Conor P. Cahill            (703)430-9247        Virtual Technologies, Inc.,
:uunet!virtech!cpcahil                           46030 Manekin Plaza, Suite 160
:                                                Sterling, VA 22170 

Obviously this fails for $# > 9.  So you need some form of shift,
preferably forking and execing as few processes as possible (nice to
avoid expr).  You'd also like to preserve the current args.  So try
this:

args="$@"      # get arglist
argno=$#       # and number of current args
export args argno
last=`set -- spacer $args   # set as args - add spacer to eliminate expr
shift $argno          # shift over
echo $1`

This works if args contains no args with embedded whitespace - if that
is a possibility,  then expr must be used.

args="$@"
export args argno
last=` set -- spacer $args
shift \`expr $# - 1\`
echo $1`
echo $last

But this also fails if last arg may contain whitespace.  Oh well!

-- 
Dan Mercer
NCR Network Products Division      -        Network Integration Services
Reply-To: mercer@npdiss1.StPaul.NCR.COM (Dan Mercer)
"MAN - the only one word oxymoron in the English Language"
Re: Finding the last arg
#2014
Author: Martin Weitzel
Date: Wed, 02 Jan 1991 10:37
41 lines
2081 bytes
In article <1990Dec27.060903.1604@onion.pdx.com> jeff@onion.pdx.com (Jeff Beadles) writes:
>In <18476@shlump.nac.dec.com> lan_csse@netrix.nac.dec.com writes:
>>The problem is that I can't figure out any Bourne-shell expression that
>>gives the last argument.
>...
>>Any ideas?
>
>Well, there's nothing that I am aware of in /bin/sh that will ALWAYS allow
>this to work.  The problem is that if you have more than 9 arguements, you
>have to shift them off to get to what you want.

Not really. I have waited some time, but as Chris Torek seems to be in
vacation I'll bite the bullet this time :-) [%].

What ALLWAYS works in the Bourne-Shell is this:

	for last do :; done

Explanation: The for loop loops over all arguments of the current procedure,
if you leave out the `in'-clause. Every argument is put into the variable
you name after `for'. This leaves the last argument in last when the loop is
through.

You can also access the second last (third last, etc) argument if you
extend this trick a little:

	for i
	do
		last3=$last2
		last2=$last
		last=$i
	done

And please DON'T use `for i in $*' instead of leaving the `in'-clause out!
It's not the same - despite some UNIX books claim so!! (For the nit-pickers:
`for i' is the same as `for i in ${1+"$@"}' i.e. you will never run into
problems if there are no arguments or some arguments have embedded blanks.)

%: Shouldn't this be in the FAQ-list or is it? (I've no recent copy available)
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83
Re: Finding the last arg
#2012
Author: Chet Ramey
Date: Wed, 02 Jan 1991 17:41
50 lines
2740 bytes
>>In fact, they need to be BSD bourne shell
>>scripts rather that ATT Bourne shell scripts.  The difference is probably
>>significant here, because of the differences in the way these two Bourne
>
>  As an aside, could you explain this comment?  I have had minimal contact
>  with BSD, but my experience does not seem to support this statement.

The BSD /bin/sh is the one from v7, with minimal changes for the 4.2 BSD
signal semantics (restarted system calls, etc.) and # as a comment
character.  The AT&T /bin/sh changed drastically beginning with System V.2,
and further changes appeared in System V.3. 

Here's a short list of what was added to the v7 sh for the System V.2 sh:

shell functions and the `return' builtin
redirection of input/output for builtins (e.g. `read x < /dev/tty')
command hashing, `set -h', and the hash builtin
test/[, echo, pwd, ulimit builtins
exit status of a pipeline was defined to be the exit status of the last command
set -a, set -f
# as the comment character (this was put in at Berkeley and adopted by AT&T)
colon form of parameter substitution to test for nullness of a variable
CDPATH
MAILCHECK, MAILPATH
the use of `!' to negate a [] pattern for filename generation
the `<<-' form of here-document redirection (strip leading tabs)
set --
numeric parameter to the shift builtin
the unset builtin (!)
restricted shell mode

There were a few changes between the System V.2 sh and the V.3 sh, in
addition to bug fixes:

the shell is now 8-bit clean (that is, the shell no longer implements quoting
by turning on the 8th bit of characters)
function arguments no longer overwrite the global set of positional parameters
the getopts builtin

The BSD /bin/sh, which is still pretty much the V7 /bin/sh, includes
`login' as a builtin and allows `chdir' as a synonym for `cd'.  The
System V shell includes `newgrp' as a builtin.  As of 4.3 BSD, the BSD sh
accepts # as a comment only when non-interactive.

Chet
-- 
Chet Ramey				``I die, Horatio''
Network Services Group, Case Western Reserve University
chet@ins.CWRU.Edu
                My opinions are just those, and mine alone.
Re: Finding the last arg
#2016
Author: Bob McGowen x4312 dept208
Date: Wed, 02 Jan 1991 20:12
40 lines
2194 bytes
In article <1991Jan2.174157.21530@usenet.ins.cwru.edu> chet@po.CWRU.Edu writes:
>>>In fact, they need to be BSD bourne shell
>>>scripts rather that ATT Bourne shell scripts.  The difference is probably
>>>significant here, because of the differences in the way these two Bourne
>>
>>  As an aside, could you explain this comment?  I have had minimal contact
>>  with BSD, but my experience does not seem to support this statement.
>
>The BSD /bin/sh is the one from v7, with minimal changes for the 4.2 BSD
>signal semantics (restarted system calls, etc.) and # as a comment
>character.  The AT&T /bin/sh changed drastically beginning with System V.2,
>and further changes appeared in System V.3. 
>
>Here's a short list of what was added to the v7 sh for the System V.2 sh:

---description of differences deleted---

>accepts # as a comment only when non-interactive.
>
>Chet
>-- 

Thanks for the list of differences.  It will be useful, I'm sure!-)

However, the original posters statement was that there were differences
in how the versions of the shells handle "variables", which my 
experience seems to show is either the same or very nearly so, since
all of the scripts I have written work with both BSD and AT&T sh's.
So, even though the above list is very useful, the specific point of
possible differences in how the shells handle parameters (command
line arguments and variables) is still open.

Any information on this particular point would be helpful.

Thanks,

Bob McGowan  (standard disclaimer, these are my own ...)
Product Support, Wyse Technology, San Jose, CA
..!uunet!wyse!bob
bob@wyse.com
Re: Finding the last arg
#2017
Author: Hans Mulder
Date: Thu, 03 Jan 1991 21:47
27 lines
1406 bytes
In article <1991Jan2.174157.21530@usenet.ins.cwru.edu> chet@po.CWRU.Edu writes:
>Here's a short list of what was added to the v7 sh for the System V.2 sh:

[ stuff deleted ]

>exit status of a pipeline was defined to be the exit status of the last command

Nit-pick:
Both the v7 and the SVR2 sh manuals mention that
"the exit status of a pipeline is the exit status of the last command".

The problem is the the word "last" is ambiguous.

The v7 sh takes it to mean "last to terminate", SVR2 interprets it as
"rightmost".

Thus the pipeline "sleep 10 | false" would be considered successful by
the v7 shell (since the sleep terminates last, and successfully), while
the SVR2 sh considers it unsuccessful.

I think the latter behaviour is more useful.  For one thing, it makes
"if foo | grep -s bar" work right 100% of the time.


Happy New Year,

Hans Mulder	hansm@cs.kun.nl
Re: Finding the last arg
#2024
Author: Larry Wall
Date: Thu, 03 Jan 1991 23:55
11 lines
1020 bytes
In article <2577@wn1.sci.kun.nl> hansm@cs.kun.nl (Hans Mulder) writes:
: I think the latter behaviour is more useful.  For one thing, it makes
: "if foo | grep -s bar" work right 100% of the time.

Presuming, of course, that your grep actually returns a reasonable
exit status.  Some don't, alas.

And the moral of the story?  It's easier to port a shell than a shell script.

Larry Wall
lwall@jpl-devvax.jpl.nasa.gov
sh differences: v7 vs. SVr2
#2019
Author: Darin McGrew
Date: Fri, 04 Jan 1991 00:36
26 lines
1477 bytes
In article <2577@wn1.sci.kun.nl> hansm@cs.kun.nl (Hans Mulder) writes:
>Both the v7 and the SVR2 sh manuals mention that
>"the exit status of a pipeline is the exit status of the last command".
>
>The problem is the the word "last" is ambiguous.
>
>The v7 sh takes it to mean "last to terminate", SVR2 interprets it as
>"rightmost".

An interesting glitch I ran into with a sh from a BSD port
(running SunOS 4.1, it works as expected).  The script

	(sleep 5; false)&
	if (sleep 10)
	then echo true
	else echo false
	fi

would echo "false" because the exit status from the backgrounded
subshell would clobber the exit status of other subshell.  This
caused all manner of havoc in a script that forked several
background shells, and figuring out what was going wrong was a
lot of fun.

Darin McGrew			mcgrew@Eng.Sun.COM
Affiliation stated for identification purposes only.
Re: Finding the last arg
#2026
Author: Stuart Poulin
Date: Sat, 05 Jan 1991 01:28
17 lines
1175 bytes
Of course if you know the last arg will never have white space and
the command line wont have args like "=", you can use expr:
	Last=`expr "$*" : '.* \(.*\)' \| "$*"`

	-or-
Use awk, it can handle a "=" by itself but still no white space.
	Last=`Last=`echo "$*" | awk '{ print $NF }'`

    -or-
Something with sed or grep. 

I like the "for last do :; done" method - it's very clever and always works. 

Stuart Poulin                             DNS: stuart@amc.com
Applications Engineer/System Administrator
Applied Microsystems Corporation         UUCP: amc-gw!stuart
Redmond, Washington  98073               Dial: 800-ASK-4AMC,206-882-2000 
Re: Finding the last arg
#2023
Author: Brandon S. Allbery KB8JRR
Date: Sat, 05 Jan 1991 03:05
28 lines
1763 bytes
As quoted from <1991Jan2.174157.21530@usenet.ins.cwru.edu> by chet@odin.INS.CWRU.Edu (Chet Ramey):
+---------------
| Here's a short list of what was added to the v7 sh for the System V.2 sh:
| 
| colon form of parameter substitution to test for nullness of a variable
| set --
| numeric parameter to the shift builtin
| restricted shell mode
+---------------

Those four were in the System III /bin/sh.

+---------------
| System V shell includes `newgrp' as a builtin.  As of 4.3 BSD, the BSD sh
| accepts # as a comment only when non-interactive.
+---------------

Silliness.  I disliked this in csh, I devoutly hope AT&T didn't pick it up for
V.4 /bin/sh.  Why should # be a comment only when noninteractive?  There are
valid reasons to use comments during interactive sessions --- *especially*
under BSD, which has a working (read: pty-based) "script" command....

++Brandon
-- 
Me: Brandon S. Allbery			    VHF/UHF: KB8JRR on 220, 2m, 440
Internet: allbery@NCoast.ORG		    Packet: KB8JRR @ WA8BXN
America OnLine: KB8JRR			    AMPR: KB8JRR.AmPR.ORG [44.70.4.88]
uunet!usenet.ins.cwru.edu!ncoast!allbery    Delphi: ALLBERY
Re: Bourne-shell incompatibilities
#2030
Author: John Chambers
Date: Sat, 05 Jan 1991 14:35
25 lines
1677 bytes
> >The problem is that I can't figure out any Bourne-shell expression that
> >gives the last argument.  In C-shell, it's easy (argv[$#]).  But I need
> >to write Bourne shell scripts.  In fact, they need to be BSD bourne shell
> >scripts rather that ATT Bourne shell scripts.  The difference is probably
> >significant here, because of the differences in the way these two Bourne
> 
>   As an aside, could you explain this comment?  I have had minimal contact
>   with BSD, but my experience does not seem to support this statement.

Well, one difference that has bitten me is
	${x-foo}
versus:
	${x:-foo}

Often only one of these is accepted by any particular /bin/sh, and the
other is a syntax error.  Sometimes both are accepted (with different
meanings).  BSD /bin/sh usually only understands the first format, and
the script dies if you use the second format.

(Isn't it wonderful to have such a nice tool for portable scripts? ;-)
-- 
Zippy-Says: Imagine ... a world without clothing folds, chiaroscuro, or marital difficulties ...
Home: 1-617-484-6393 Work: 1-508-952-3274
Uucp: ...!{harvard.edu,ima.com,eddie.mit.edu,ora.com}!minya!jc (John Chambers)
Uucp-map: minya	adelie(DEAD)
Re: Finding the last arg
#2031
Author: John Chambers
Date: Sat, 05 Jan 1991 15:56
55 lines
2675 bytes
> What ALLWAYS works in the Bourne-Shell is this:
> 
> 	for last do :; done

Wow! A one-liner that works for more than 9 args!  Of course, there's 
the question as to whether this loop is actually faster than starting 
a subprocess that just does puts(argv[artc-1]), but at least there's
a way to do it that is portable.

That comment isn't worth wasting the bandwidth, of course; my motive
for this followup is a bit of bizarreness that I discovered while
testing this command.  The usual format of a for loop is 3 lines:
	for last
	do :
	done
Usually when I want to collapse such vertical code into a horizontal
format, I follow the rule "Replace the newlines with semicolons", and
it works.  For instance,
	if [ <test> ]
	then <stuff>
	else <stuff>
	fi
reduces to
	if [ <test> ];then <stuff>;else <stuff>;fi
which I can do in vi via a series of "Jr;" commands.  With the above 
for-loop, this gives
	for last;do :;done
which doesn't work.  The shell gives a syntax error, complaining about
an unexpected ';' in the line.  Myself, I found this to be a somewhat 
unexpected error message.  It appears my simple-minded algorithm for 
condensing code doesn't work in this case.

So what's going on here?  What the @#$^&#( is the shell's syntax that 
makes the semicolon not only unneeded, but illegal in this case?

One of the real hassles I keep finding with /bin/sh (and /bin/csh is
even worse ;-) is that the actual syntax regarding things like white
space, newlines, and semicolons seems to be a secret.  It often takes 
a lot of experimenting to find a way to get these syntax characters 
right.  Is there any actual documentation on sh's syntax?  Is it truly 
as ad-hoc as the above example implies?  Is there perhaps some logical 
structure underlying it all that would explain why
 	for last do :; done
and
	for last
	do :
	done
both work but
	for last;do :;done
doesn't?
-- 
Zippy-Says: Imagine ... a world without clothing folds, chiaroscuro, or marital difficulties ...
Home: 1-617-484-6393 Work: 1-508-952-3274
Uucp: ...!{harvard.edu,ima.com,eddie.mit.edu,ora.com}!minya!jc (John Chambers)
Uucp-map: minya	adelie(DEAD)
Re: Bourne-shell incompatibilities
#2032
Author: Dan_Jacobson
Date: Sun, 06 Jan 1991 17:41
14 lines
1047 bytes
>>>>> On 5 Jan 91 14:35:27 GMT, jc@minya.UUCP (John Chambers) said:

John> 	${x-foo}
John> versus:
John> 	${x:-foo}

John> Often only one of these is accepted by any particular /bin/sh, and the
John> other is a syntax error.

My understanding is that the first should always work, and the second
should work in newer /bin/sh's with this added bonus feature [BSD 4.3
/bin/sh doesn't accept it, like you mentioned].
-- 
Dan_Jacobson@ATT.COM  Naperville IL USA  +1 708-979-6364
Re: Finding the last arg
#2033
Author: Dan_Jacobson
Date: Sun, 06 Jan 1991 18:06
10 lines
1031 bytes
>>>>> On 5 Jan 91 15:56:45 GMT, jc@minya.UUCP (John Chambers) said
	among many other things that I was too lasy to go downstirs to get the
	book to look up [it's cold down there]:

John> Is there any actual documentation on sh's syntax?

In the 4.3 BSD document set you'll find a syntax description in the sh
articles in [I think] the User's Supplementary Documents volume.
-- 
Dan_Jacobson@ATT.COM  Naperville IL USA  +1 708-979-6364
Understanding the Bourne Shell (was Re: Finding the last arg)
#2039
Author: Martin Weitzel
Date: Mon, 07 Jan 1991 20:11
308 lines
14579 bytes
In article <443@minya.UUCP> jc@minya.UUCP (John Chambers) writes:
>> What ALLWAYS works in the Bourne-Shell is this:
>> 
>> 	for last do :; done
>
>Wow! A one-liner that works for more than 9 args!  Of course, there's 
>the question as to whether this loop is actually faster than starting 
>a subprocess that just does puts(argv[artc-1]), but at least there's
>a way to do it that is portable.

I have compared the alternatives here on my 386 box and as you might guess
the differences in speed depends on the length of the argument list.

For ~25 arguments the for-loop is the fastest, above that up to ~100
arguments there's few difference, but the for loop uses more usr-time and
the sub-process more sys-time. There seem to be minor differences between
what is called as sub-process, i.e. a specialized C program (as the poster
suggested) or another shell-script (as Maarten Litmaath posted earlier in
this thread).

For the rather untypical size of 250 arguments there still isn't much
difference but sometimes the sub-process is faster (the results vary over
some range and I didn't go into the efforts to calculate the average).
My general experience with the 386 is that it starts sub-processes really
fast, so I think the for-do method will even win even for more than 250
arguments on a lot of systems.

(BTW: I've learned by my experiments that the shell internally limits
the number of arguments that can be passed to a sub process to 254.
I allways thought the only limit were the space supplied by the OS
to pass the stuff to the sub-process, which is typically several KByte
for the *contents* of arguments + environment. I never noticed the limit
on the *number* of arguments before.)

>That comment isn't worth wasting the bandwidth, of course; my motive
>for this followup is a bit of bizarreness that I discovered while
>testing this command.  The usual format of a for loop is 3 lines:
>	for last
>	do :
>	done
>Usually when I want to collapse such vertical code into a horizontal
>format, I follow the rule "Replace the newlines with semicolons", and
>it works.  For instance,
>	if [ <test> ]
>	then <stuff>
>	else <stuff>
>	fi
>reduces to
>	if [ <test> ];then <stuff>;else <stuff>;fi
>which I can do in vi via a series of "Jr;" commands.  With the above 
>for-loop, this gives
>	for last;do :;done
>which doesn't work.  The shell gives a syntax error, complaining about
>an unexpected ';' in the line.  Myself, I found this to be a somewhat 
>unexpected error message.  It appears my simple-minded algorithm for 
>condensing code doesn't work in this case.
>
>So what's going on here?  What the @#$^&#( is the shell's syntax that 
>makes the semicolon not only unneeded, but illegal in this case?

Funny, I stumbled over the same thing when I "invented" my for-do method
for accessing the last argument some years ago. The explanation is a bit
longer, so all who aren't interested in the details should leave at this
point.

The syntax for the "for" statment is more or less the following (I stick
to the "yacc"-style here, but include keywords into single quotes even if
they are longer than one character, what is not allowed with "yacc"):


for_stmt : 'for' NAME 'in' word_list SEP 'do' cmd_list 'done'
	 | 'for' NAME 'do' cmd_list 'done'
	 ;

word_list: WORD
	 | word_list WORD

cmd_list : cmd arg_list SEP
	 | cmd_list cmd arg_list SEP
	 ;

arg_list : /*empty*/
	 | arg_list WORD
	 ;

SEP	 : ';'
	 | '\n'
	 ;

(The meaning of NAME and WORD should be obvious - I don't want to go into
the syntactic details too far. I have further left out an undocumented
shell feature, that allows you to replace "do" and "done" with "{" and "}";
note that the latter is only true for for-do-done, not for while-do-done
and until-do-done!)

Note that white space is allowed everywhere in between the tokens
and nonterminals. But SEP is a mandatory seperator (which can be
a newline or a semicolon). The reason for requiring a separator in
some cases is simple: There is the possibility that some keywords of
the shell might also be used as regular argument to commands or within
a word_list - we'll come back to this in a moment.

The shell detects the two forms of the "for" statement simply by looking
at what follows the loop-variable. If it is an "in" then there must also
follow a word_list, which in turn must be terminated by a mandatory
seperator, as explained above. If there follows a "do" there is no
wordlist. If there follows a semicolon after the loop-variable, this
is against the syntax (this was what the poster puzzled).

Of course, Mr. Bourne could have made the syntax to allow for it by
changing the RHS of the rule for the "for" statement without "in" into

	'for' NAME SEP 'do' cmd_list 'done'

but IMHO the difficulties of the poster (and many more, me included)
have some other reason, that has something to do with the difference
between
	- mandatory command separators resp. terminators and
	- optional white space before commands and keywords and
	- spaces as separators of command and argument list and
	- the semicolon beeing allowed only in the first case and
	- the newline beeing allowed in the first and second case,
	- space characters beeing allowed in the second and third.

In a simple command, i.e. a programm name that is followed by some arguments,
there's not much of a problem as it seems "natural" for most users to type
spaces to separate the arguments and newlines to terminate commands and it
seems obvious that the two can not be used interchangable, as this either
would terminate the argument list prematurely (if you try to separate
arguments with a newline) or it doesn't properly end your command (if you
don't type newline). 

Now let's consider the more complex shell statements. Some very stupid
users might in fact expect that the shell can read their mind, but all the
others will understand that the shell must either treat ALL keywords (and
maybe even all the commands) special, not allowing them as regular arguments,
or needs some other separator as the one used between arguments, if there
shall follow a keyword after a command (or there shall be two commands) in
the same line. The logic can be applied to most keywords regardless if
they introduce some complex command or if they mark the beginning of the
next part of the command (like "then" or "else" in an "if" statement).

More puzzling is that the shell also ALLOWS newlines in place of spaces
where it's clear that a complex command isn't complete%. One place where
this occurs is when you start a "for" statement and have not yet supplied
the matching "done".  For example

	for var in foo bar
		<some newlines here (1)>
	do	<some newlines here (2)>
		cmd
		<some newlines here (3)>
	done

is all allowed, though seldom used, except for exactly one newline in
the place marked (2). Note that the newlines before and after "cmd" here
can not simply be seen as "empty commands", because if they could, the
following would be legal:

	for var in foo bar
	do
	done

which IS NOT, since there is at least ONE command necessary between "do"
and "done" (please refer to the syntax given above). Note further that a
semicolon by itself is NOT an empty command, as

	for var in foo bar
	do ;
	done

does not work - you need at least the colon here:

	for var in foo bar
	do :
	done

------
%: More puzzling is that the shell does only allow it in some places.
   E.g. "for <newline>" is a syntax error while "for i <newline>"
   patiently waites for the "in" or "do".
------

>One of the real hassles I keep finding with /bin/sh (and /bin/csh is
>even worse ;-) is that the actual syntax regarding things like white
>space, newlines, and semicolons seems to be a secret.  It often takes 
>a lot of experimenting to find a way to get these syntax characters 
>right.  Is there any actual documentation on sh's syntax?  Is it truly 
>as ad-hoc as the above example implies?

For all I know the C-shell is more or less "ad-hoc", but for the Bourne
shell (which, until now and for the rest of this article, I allways mean
when I speak of "the shell") you can find a formal syntax allready in a
very ancient document, the "Bell Systems Technical Journal" (BSTJ in short)
from July/August 1978, ISSN0005-8580. The grammar starts on page 1987 as
Appendix A of an article written by S.R. Bourne himself. Though it fails
to mention some of the finer points (like the space/newline problems just
discussed) it may serve as a start for you and I found that it could even
be fed to yacc without much problems (I never tried to fill in the actions
to make it work as a "real" shell ...)

>Is there perhaps some logical 
>structure underlying it all that would explain why
> 	for last do :; done
>and
>	for last
>	do :
>	done
>both work but
>	for last;do :;done
>doesn't?

Well, "logic" is not so much an absolut value as many of us think, as it
often depends on what you expect. This is so because we may think we
have recognized something as a "rule" and tend to see all withstanding
observations as "illogical", where just the examples we studied were too
limited to recognize that we had only a seen special case (in this generality
that may also be true for the things we consider to be the "universal
laws" or "laws of nature" - but this brings us away from the topic.)

Now, what you observed were that newline and semicolon are interchangable
in all the examples you looked at and have tried before you came to that
"for" statement. (Remember I told you in the beginning that I had the same
problem with this - so it can not be said that your expectations were
without reason.) A bit more experimentation could also have shown that in
general the both are not really interchangable. E.g. if you type a single
newline nothing happens (except the shell prompts again), if you type two
newlines still nothing happens but if you type a semicolon + a newline this
is a syntax error. Hence semicolon and newline are not so much
interchangable as it seemed on first glance.

Now, having a little more experience we can come up with some other
explanation:

	- commands can not be empty (they consist at least of
	  an external or builtin command; the ":" is the builtin
	  command which does nothing but evaluate its arguments)
	- a semicolon or a newline% terminates a command
	- a command list is a non-empty sequence of commands, all
	  of which must be properly terminated
	- a semicolon or a newline terminates the word list of
	  the "in" part of the "for" statement
	- space characters and newlines are allowed before commands
	- nearly all the keywords of the shell are only recognized if
	  they are found in the position of a command, i.e. if there is
	  a previous command or a word list of a "for" statement there
	  MUST be a separator and their CAN be some space characters or
	  newlines
	- the most important exceptions from the above are "in" (as
	  well for the "for" statement as for the "case" statement) and
	  "do". But as the word list in the "in" part of a "for" statement
	  (or the command list after the "while" or "until" in such a
	  statement) must be properly terminated, a "do" NOT in command
	  position can only occur in a "in"-less "for" statement.

-----
%: There are other valid command separators/terminators that are recognized
together with the semicolon, but this doesn't matter here.
-----

In some sense, this are the "laws of nature" as derived from observing
the shell's behaviour. As the shell is not really nature but the outcome
of the thoughts of some human beeing, we could of course complain now
that this is "illogical" (compared to our sense logic!) or that there
are "too many exceptions" and that it could be simplified with fewer,
but more general rules.

But when thinking how to smoothen things out by using fewer rules, we
often do not recognize all the consequences that this would have.
Assume for a momemt we would treat both, newline and semicolon, as
statement terminator. Have you really considered what this would mean?
Typing a newline (at your terminal or as empty line in a shell script)
would be a syntax error (sic!) as a single semicolon is. Quite simple
I hear you say, then we allow for an empty statement to be really empty,
which would allow for single newlines as well as single semicolons. But
be careful! We then must think about the exit status of such a statement.
Should it allways be true as the colon command? But then you must be very
careful inserting empty lines into a script, because the following two
would have different semantics

	if		|		if	cmd
		cmd	|
	then		|		then

and you must never separate command execution and accessing $? by a
newline, since the empty command "newline" destroys the value of any
previous command's exit status. Again I hear you say, we make the
empty statment special - it shall leave the status of the "real" command
that was executed last. But now the following will become dangerous

	while
	do
		<do something until exit or break>
	done

as it depends on the last command BEFORE the loop when the loop is
entered the first time, and after that on the last command executed
WITHIN the loop. So, step by step we may introduce more special casing
for something that looked like a trivial change in the first place!

I hope you have gained a little more understanding for the syntax of the
shell now. It isn't really as strange as it might seem on first glance,
though I admit a few things are not so obvious and it's easy to come to
some wrong conclusions if you have insufficient experience. (If this
article hadn't become that long I could write a little more on it - maybe
some other time.)
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83
Re: Understanding the Bourne Shell (was Re: Finding the last arg)
#2323
Author: Eric Gisin
Date: Tue, 08 Jan 1991 14:29
18 lines
987 bytes
The shell's interpretation of newline is context sensitive.
It is usually equivalent to ";", but in a few cases it
is equivalent to white-space (space or tab). The latter cases
include after "|", "&&", "||", "for NAME", and "case WORD".

So all the following are valid:
$ ls |
> wc
$ true &&
> false ||
> maybe
$ for x
> in a b c
> do :
> done
$ case x
> in x) echo x!		# ;; optional here
> esac
Re: Bourne-shell incompatibilities
#2322
Author: Wm E. Davidsen Jr
Date: Wed, 09 Jan 1991 01:52
20 lines
1381 bytes
In article <442@minya.UUCP> jc@minya.UUCP (John Chambers) writes:

| Often only one of these is accepted by any particular /bin/sh, and the
| other is a syntax error.  Sometimes both are accepted (with different
| meanings).  

  I don't dispute what you say, but I have never seen a sh which didn't
buy {$a-b} notation. Of course the meaning is different when both are
available, the whole : set of expansions was added about SysIII (my
SysIII system is down or I'd check).

${a-b}		if $a is undefined use b instead
${a:-b}		if $a is undefined *or null* use b instead

and so on for all the other operators.
-- 
bill davidsen - davidsen@sixhub.uucp (uunet!crdgw1!sixhub!davidsen)
    sysop *IX BBS and Public Access UNIX
    moderator of comp.binaries.ibm.pc and 80386 mailing list
"Stupidity, like virtue, is its own reward" -me
Re: Finding the last arg
#2378
Author: Chuck.Phillips
Date: Mon, 14 Jan 1991 15:43
63 lines
3371 bytes
>>>>> On 5 Jan 91 15:56:45 GMT, jc@minya.UUCP (John Chambers) said:
John> With the above for-loop, this gives
John> 	for last;do :;done
John> which doesn't work.  The shell gives a syntax error, complaining
John> about an unexpected ';' in the line.  Myself, I found this to be a
John> somewhat unexpected error message.  It appears my simple-minded
John> algorithm for condensing code doesn't work in this case.

John> So what's going on here?  What the @#$^&#( is the shell's syntax that 
John> makes the semicolon not only unneeded, but illegal in this case?

A little sh history: sh comments originally went from a ':' to the end of a
line.  ('#'-style comments were added later.)  The funny thing about ':'
comments is they get evaluated and have a return value, unlike '#' comments
which are merely whitespace.

For example:

: Arbitrary text here > foop

This "comment" wipes out the file foop, if it exists, then creates a null
file called "foop".  (Wonder why they added '#' comments? ;^)  The shell
does not attempt to execute a command called "Arbitrary", and the comment
has a return value of zero (a.k.a. success).

':' statements have other uses besides a filler with a return value of '0'
and an obscure way to create null files.

: ${VARIABLE=value}

Sets variable VARIABLE to "value" unless VARIABLE is already set.  Lastly,
beginning a sh script with ":" instead of "#! /bin/sh" saves an unnecessary
fork and exec on some versions of UNIX.  (Note to bash users: In bash,
scripts beginning with ":" are interpreted by bash while "#! /bin/sh"
scripts are, of course, executed by "/bin/sh".)


To answer your original question:

for last;do :;done

The ";done" part of the statement is a comment, which yields a syntax
error.  (sh error messages are often misleading, IMHO.)

John> One of the real hassles I keep finding with /bin/sh (and /bin/csh is
John> even worse ;-) is that the actual syntax regarding things like white
John> space, newlines, and semicolons seems to be a secret.

Oh, that reminds me.  You're not to repeat anything I've said.  :-)
I agree that csh is worse, mostly due to its (mis)handling of newlines.  I
still use csh interactively, but program in sh after being burned a few
times by csh's unexpected syntax exceptions and variations.  (Just try
piping the result of one loop into another loop in csh without using a
temporary file, FIFO or multiple invocations of csh.)

#include <std/disclaimer.h>

	Hope this helps,
--
Chuck Phillips  MS440
NCR Microelectronics 			chuck.phillips%ftcollins.ncr.com
2001 Danfield Ct.
Ft. Collins, CO.  80525   		...uunet!ncrlnk!ncr-mpd!bach!chuckp
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads