From owner-freebsd-questions@freebsd.org Sat Feb 13 13:03:45 2016 Return-Path: Delivered-To: freebsd-questions@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 4FF3FAA719B for ; Sat, 13 Feb 2016 13:03:45 +0000 (UTC) (envelope-from smithi@nimnet.asn.au) Received: from sola.nimnet.asn.au (paqi.nimnet.asn.au [115.70.110.159]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id B424A1886 for ; Sat, 13 Feb 2016 13:03:43 +0000 (UTC) (envelope-from smithi@nimnet.asn.au) Received: from localhost (localhost [127.0.0.1]) by sola.nimnet.asn.au (8.14.2/8.14.2) with ESMTP id u1DD3ci9054585; Sun, 14 Feb 2016 00:03:39 +1100 (EST) (envelope-from smithi@nimnet.asn.au) Date: Sun, 14 Feb 2016 00:03:38 +1100 (EST) From: Ian Smith To: Jan Henrik Sylvester cc: Sergei G , freebsd-questions@freebsd.org Subject: Re: /bin/sh starts with check in script In-Reply-To: <56BE3085.9040505@janh.de> Message-ID: <20160213223711.B51785@sola.nimnet.asn.au> References: <20160213034617.D51785@sola.nimnet.asn.au> <56BE3085.9040505@janh.de> MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII X-BeenThere: freebsd-questions@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: User questions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 13 Feb 2016 13:03:45 -0000 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 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