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
Author: CSSE LAN Test Account
Date: Wed, 26 Dec 1990 16:19
Date: Wed, 26 Dec 1990 16:19
35 lines
2234 bytes
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
Author: Rouben Rostamian
Date: Wed, 26 Dec 1990 18:50
Date: Wed, 26 Dec 1990 18:50
22 lines
1565 bytes
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
Author: Bob McGowen x4312 dept208
Date: Wed, 26 Dec 1990 22:44
Date: Wed, 26 Dec 1990 22:44
84 lines
3928 bytes
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
Author: Jeff Beadles
Date: Thu, 27 Dec 1990 06:09
Date: Thu, 27 Dec 1990 06:09
40 lines
1453 bytes
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
Author: Conor P. Cahill
Date: Thu, 27 Dec 1990 15:49
Date: Thu, 27 Dec 1990 15:49
11 lines
970 bytes
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
Author: Maarten Litmaath
Date: Thu, 27 Dec 1990 22:15
Date: Thu, 27 Dec 1990 22:15
34 lines
1163 bytes
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)
Author: Maarten Litmaath
Date: Sat, 29 Dec 1990 00:51
Date: Sat, 29 Dec 1990 00:51
55 lines
1488 bytes
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
Author: Wm E. Davidsen Jr
Date: Mon, 31 Dec 1990 04:16
Date: Mon, 31 Dec 1990 04:16
20 lines
1518 bytes
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
Author: Lawrence V. Cipriani
Date: Tue, 01 Jan 1991 15:57
Date: Tue, 01 Jan 1991 15:57
18 lines
938 bytes
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
Author: Dan Mercer
Date: Tue, 01 Jan 1991 23:42
Date: Tue, 01 Jan 1991 23:42
42 lines
2081 bytes
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
Author: Martin Weitzel
Date: Wed, 02 Jan 1991 10:37
Date: Wed, 02 Jan 1991 10:37
41 lines
2081 bytes
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
Author: Chet Ramey
Date: Wed, 02 Jan 1991 17:41
Date: Wed, 02 Jan 1991 17:41
50 lines
2740 bytes
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
Author: Bob McGowen x4312 dept208
Date: Wed, 02 Jan 1991 20:12
Date: Wed, 02 Jan 1991 20:12
40 lines
2194 bytes
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
Author: Hans Mulder
Date: Thu, 03 Jan 1991 21:47
Date: Thu, 03 Jan 1991 21:47
27 lines
1406 bytes
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
Author: Larry Wall
Date: Thu, 03 Jan 1991 23:55
Date: Thu, 03 Jan 1991 23:55
11 lines
1020 bytes
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
Author: Darin McGrew
Date: Fri, 04 Jan 1991 00:36
Date: Fri, 04 Jan 1991 00:36
26 lines
1477 bytes
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
Author: Stuart Poulin
Date: Sat, 05 Jan 1991 01:28
Date: Sat, 05 Jan 1991 01:28
17 lines
1175 bytes
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
Author: Brandon S. Allbery KB8JRR
Date: Sat, 05 Jan 1991 03:05
Date: Sat, 05 Jan 1991 03:05
28 lines
1763 bytes
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
Author: John Chambers
Date: Sat, 05 Jan 1991 14:35
Date: Sat, 05 Jan 1991 14:35
25 lines
1677 bytes
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
Author: John Chambers
Date: Sat, 05 Jan 1991 15:56
Date: Sat, 05 Jan 1991 15:56
55 lines
2675 bytes
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
Author: Dan_Jacobson
Date: Sun, 06 Jan 1991 17:41
Date: Sun, 06 Jan 1991 17:41
14 lines
1047 bytes
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
Author: Dan_Jacobson
Date: Sun, 06 Jan 1991 18:06
Date: Sun, 06 Jan 1991 18:06
10 lines
1031 bytes
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)
Author: Martin Weitzel
Date: Mon, 07 Jan 1991 20:11
Date: Mon, 07 Jan 1991 20:11
308 lines
14579 bytes
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)
Author: Eric Gisin
Date: Tue, 08 Jan 1991 14:29
Date: Tue, 08 Jan 1991 14:29
18 lines
987 bytes
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
Author: Wm E. Davidsen Jr
Date: Wed, 09 Jan 1991 01:52
Date: Wed, 09 Jan 1991 01:52
20 lines
1381 bytes
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
Author: Chuck.Phillips
Date: Mon, 14 Jan 1991 15:43
Date: Mon, 14 Jan 1991 15:43
63 lines
3371 bytes
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