Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 14 Feb 2016 00:03:38 +1100 (EST)
From:      Ian Smith <smithi@nimnet.asn.au>
To:        Jan Henrik Sylvester <me@janh.de>
Cc:        Sergei G <sergeig.public@gmail.com>, freebsd-questions@freebsd.org
Subject:   Re: /bin/sh starts with check in script
Message-ID:  <20160213223711.B51785@sola.nimnet.asn.au>
In-Reply-To: <56BE3085.9040505@janh.de>
References:  <mailman.83.1455278401.97224.freebsd-questions@freebsd.org> <20160213034617.D51785@sola.nimnet.asn.au> <56BE3085.9040505@janh.de>

next in thread | previous in thread | raw e-mail | index | archive | help
On Fri, 12 Feb 2016 20:20:37 +0100, Jan Henrik Sylvester wrote:
 > On 02/12/2016 18:53, Ian Smith wrote:
 > > In freebsd-questions Digest, Vol 610, Issue 5, Message: 1
 > > On Thu, 11 Feb 2016 15:29:22 +0100 Jan Henrik Sylvester <me@janh.de> wrote:
[..]
 > >  > [ "${line#\#}" != "$line" ] && echo comment
 > >  > 
 > >  > See the Parameter Expansion section of sh(1).
 > > 
 > > That looks like it ought to work, but does not here on stable/9; I don't 
 > > think it honours the escaping when expecting a second '#' or other char?
 > 
 > I am rather curious about this, because I have been using something like
 > "${line#'#'}" for a long time, but wanted to save a character here.
 > 
 > I just extracted kernel and base from a 9.3-RELEASE image to a directory
 > and did a chroot into it, started sh there and tried:
 > 
 > line='#COMMENT'
 > [ "${line#\#}" != "$line" ] && echo comment
 > 
 > It worked.
 > 
 > I lack the time to set up a proper VM with 9/stable, but I would not
 > think the kernel mattered. Are you sure it does not work there? What
 > does A='#C' ; echo "${A#\#}" "${A#'#'}" give on stable/9?

Sorry to have put you to that trouble .. turns out I'd done those tests 
originally on:

smithi on t23% uname -mr
8.2-RELEASE i386
smithi on t23% sh
$ A='#C' ; echo "${A#\#}" "${A#'#'}"
#C #C

But you're quite right, these work fine on:

smithi@x200:~ % uname -rm
9.3-STABLE amd64
smithi@x200:~ % sh
$ A='#C' ; echo "${A#\#}" "${A#'#'}"
C C

which is surprising; I thought sh(1) was unlikely to have changed in 
that sort of respect.  I have plenty of sh scripts that I run on both 
boxes and don't recall ever finding an incompatibility between them, but 
most of them are pretty vanilla in terms of parameter substitutions.

 > > After some playing, this also finds comment lines that have optional 
 > > whitespace before the first '#', not pinning comments only to column 1, 
 > > while ignoring comments after other text.  Not what everybody needs ..
 > > 
 > > [ ! "`echo ${line%%#*} | tr -d [:blank:]`" ] && echo comment
 > > 
 > > BUG|FEATURE: if $line is the null string it is taken to be a comment.  
 > 
 > If you have that in a loop for every line in a long file, the process
 > spawning might take a lot of time. Why not use grep(1), if you do not
 > care to spawn a process? Of course, you should call grep(1) once first
 > and then process every line from the output.

Indeed; I do like the exercise of doing what's possible in sh(1) though.

 > Without spawning a process, you might do the same assuming you have not
 > changed IFS:
 > 
 > line=${line%%#*} ; [ "$line" = "${line%%[!$IFS]*}" ] && echo comment

Yes that works fine (on both!)  Also both have the default:
$ echo -n "$IFS" | hd
00000000  20 09 0a                                          | ..|
00000003

 > Or you do TAB=$(printf '\t') in the beginning and use:

or just TAB='	' works, also on the 8.2 but not the the 9.3 command line 
(tab completion?).  Thanks, always more tricks to learn, though I hardly 
ever use sh interactively.  I do use (in csh) sh -c '..cmds..' but that 
can get tricky too when you run out of types of quote marks :)

 > line=${line%%#*} ; [ "$line" = "${line%%[! $TAB]*}" ] && echo comment

Yep.  The [..] set there is Another syntax I've never used ..

 > String processing in sh(1) is so much fun... if you want to secure your
 > job with elegant use of obscurity, you can strip the leading blanks
 > first and then check for the first character directly:
 > 
 > line=${line#"${line%%[! $TAB]*}"}
 > [ "${line%"${line#?}"}" = "#" ] && echo 'line starts with #'

That works on both as well, though IFS is already set.

But there's definitely a portability issue here, from 8.2 anyway, 
regarding "${A#\#}" and "${A#'#'}".  I wonder if eg 8.4 has that too?

 > For boring readability you go more along Matthew's solution using case
 > or you do spawn one grep(1) process.

Yes.  I still haven't got the difference in sh and grep REs down pat .. 
and those used in less cmds, which are rather like egrep, but not quite.

For sh(1) scripting, there's lots of great tricks in the rc scripts of 
course, but for the most incredible parsing tricks it's hard to go past 
Devin Teske's bsdconfig scripts.  Thick to the point of opaque, but if 
you dig a bit there's gold in there - not that I remember much of it :)

cheers, Ian



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20160213223711.B51785>