/bin/sh starts with check in script

Jan Henrik Sylvester me at janh.de
Fri Feb 12 19:26:24 UTC 2016

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 at janh.de> wrote:
>  > On 02/10/2016 13:58, Sergei G wrote:
>  > > I came up with this solution to check if variable $line starts with a
>  > > hash.  Basically I am checking if line is a comment in the configuration
>  > > file.
>  > > 
>  > > #!/bin/sh
>  > > if expr "${line}" : '#.*' > /dev/null; then
>  > >   echo Ignoring comment line
>  > > fi
> expr(1) suggests preferring sh(1) for maths expressions and parsing :)
>  > > I had to redirect to /dev/null, because expr prints a number to STDOUT. 
>  > > Is there a better way to do this kind of string matching check in
>  > > /bin/sh (not bash)?
>  > 
>  > [ "${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#\#}" != "$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?

> 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.

Without spawning a process, you might do the same assuming you have not
changed IFS:

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

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

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

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 #'

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

Jan Henrik

More information about the freebsd-questions mailing list