From owner-freebsd-hackers@FreeBSD.ORG Sat Oct 9 20:25:56 2010 Return-Path: Delivered-To: freebsd-hackers@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 43555106566C for ; Sat, 9 Oct 2010 20:25:56 +0000 (UTC) (envelope-from yanegomi@gmail.com) Received: from mail-iw0-f182.google.com (mail-iw0-f182.google.com [209.85.214.182]) by mx1.freebsd.org (Postfix) with ESMTP id 0453A8FC08 for ; Sat, 9 Oct 2010 20:25:55 +0000 (UTC) Received: by iwn8 with SMTP id 8so2743882iwn.13 for ; Sat, 09 Oct 2010 13:25:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:received:sender:received :in-reply-to:references:date:x-google-sender-auth:message-id:subject :from:to:cc:content-type:content-transfer-encoding; bh=ozuMPYi+JgNGP5Ei5GjbIdqXCJ2NxG8b3OpSAFbgg4g=; b=mYIjgEtzIuiJKm1CWGA0YhAIf8Gvnsfe9L/jpzhwPTqK6Wk24fb0eHzMbVTrTJCCKs iL5/FcHem5cjU6O4MWXg66ROBB/E82/lnXhlH1lB/wBvPPzUTUj5dDDEk1TSKva421xH F4CbXMS8X7eh+cBV1td4dZGzaTAecGR7NAVQc= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:sender:in-reply-to:references:date :x-google-sender-auth:message-id:subject:from:to:cc:content-type :content-transfer-encoding; b=WbgseswH/GkcqJzaT77ietPVXiiTrdMdQQ8kpxRDd3w7YExO09Pv0iMwdr/mQ8nks6 ooH3aoX/AiJXgH/QaRDoDAyDF4lWXm9ojsBUp4r1uHJt1eIg+82QbJxdPNr533BXdsUm qRwnBlEdxQSPHymciS+8+cLSz44TNIgDURny8= MIME-Version: 1.0 Received: by 10.42.46.142 with SMTP id k14mr851500icf.310.1286655955058; Sat, 09 Oct 2010 13:25:55 -0700 (PDT) Sender: yanegomi@gmail.com Received: by 10.231.184.3 with HTTP; Sat, 9 Oct 2010 13:25:55 -0700 (PDT) In-Reply-To: <51B4504F-5AA4-47C5-BF23-FA51DE5BC8C8@vicor.com> References: <1286397912.27308.40.camel@localhost.localdomain> <51B4504F-5AA4-47C5-BF23-FA51DE5BC8C8@vicor.com> Date: Sat, 9 Oct 2010 13:25:55 -0700 X-Google-Sender-Auth: v3iSmydLVvNt1EtrmLkTwSGyL_c Message-ID: From: Garrett Cooper To: Devin Teske Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Cc: Brandon Gooch , freebsd-hackers@freebsd.org Subject: Re: sysrc -- a sysctl(8)-like utility for managing /etc/rc.conf et. al. X-BeenThere: freebsd-hackers@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Technical Discussions relating to FreeBSD List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 09 Oct 2010 20:25:56 -0000 On Wed, Oct 6, 2010 at 8:29 PM, Devin Teske wrote: > > On Oct 6, 2010, at 4:09 PM, Brandon Gooch wrote: > >> On Wed, Oct 6, 2010 at 3:45 PM, Devin Teske wrote: >>> Hello fellow freebsd-hackers, >>> >>> Long-time hacker, first-time poster. >>> >>> I'd like to share a shell script that I wrote for FreeBSD system >>> administration. >>> >> >> It seems the list ate the attachment :( > > > Here she is ^_^ Comments welcome. Hah. More nuclear reactor than bikeshed :D! > #!/bin/sh > # -*- tab-width: =A04 -*- ;; Emacs > # vi: set tabstop=3D4 =A0 =A0 :: Vi/ViM > # > # Default setting whether to dump a list of internal dependencies upon ex= it > # > : ${SYSRC_SHOW_DEPS:=3D0} > > ############################################################ GLOBALS > > # Global exit status variables > : ${SUCCESS:=3D0} > : ${FAILURE:=3D1} Should this really be set to something other than 0 or 1 by the end-user's environment? This would simplify a lot of return/exit calls... > # > # Program name > # > progname=3D"${0##*/}" > > # > # Options > # > SHOW_EQUALS=3D > SHOW_NAME=3D1 > > # Reserved for internal use > _depend=3D When documenting arguments passed to functions, I usually do something like= : # 1 - a var # 2 - another var # # ... etc because it's easier to follow for me at least. Various spots in the codebase have differing styles though (and it would be better to follow the style in /etc/rc.subr, et all for consistency, because this tool is a consumer of those APIs). > ############################################################ FUNCTION > > # fprintf $fd $fmt [ $opts ... ] > # > # Like printf, except allows you to print to a specific file-descriptor. = Useful > # for printing to stderr (fd=3D2) or some other known file-descriptor. > # > : dependency checks performed after depend-function declaration > : function ; fprintf ( ) # $fd $fmt [ $opts ... ] Dumb question. Does declaring `: dependency checks performed after depend-function declaration' and `: function' buy you anything other than readability via comments with syntax checking? > { > =A0 =A0 =A0 =A0local fd=3D$1 > =A0 =A0 =A0 =A0[ $# -gt 1 ] || return ${FAILURE-1} While working at IronPort, Doug (my tech lead) has convinced me that constructs like: if [ $# -le 1 ] then return ${FAILURE-1} fi Are a little more consistent and easier to follow than: [ $# -gt 1 ] || return ${FAILURE-1} Because some folks have a tendency to chain shell expressions, i.e. expr1 || expr2 && expr3 Instead of: if expr1 || expr2 then expr3 fi or... if ! expr1 then expr2 fi if [ $? -eq 0 ] then expr3 fi I've caught myself chaining 3 expressions together, and I broke that down into a simpler (but more longhand format), but I've caught people chaining 4+ expressions together, which just becomes unmanageable to follow (and sometimes bugs creep in because of operator ordering and expression evaluation and subshells, etc, but that's another topic for another time :)..). > =A0 =A0 =A0 =A0shift 1 > =A0 =A0 =A0 =A0printf "$@" >&$fd > } > > # eprintf $fmt [ $opts ... ] > # > # Print a message to stderr (fd=3D2). > # > : dependency checks performed after depend-function declaration > : function ; eprintf ( ) # $fmt [ $opts ... ] > { > =A0 =A0 =A0 =A0fprintf 2 "$@" > } > > # show_deps > # > # Print the current list of dependencies. > # > : dependency checks performed after depend-function declaration > : function ; show_deps ( ) # > { > =A0 =A0 =A0 =A0if [ "$SYSRC_SHOW_DEPS" =3D "1" ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0eprintf "Running internal dependency list:= \n" > > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0local d > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0for d in $_depend; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0eprintf "\t%-15ss%s\n" "$d= " "$( type "$d" )" The command(1) -v builtin is more portable than the type(1) builtin for command existence lookups (it just doesn't tell you what the particular item is that you're dealing with like type(1) does). I just learned that it also handles other builtin lexicon like if, for, while, then, do, done, etc on FreeBSD at least; POSIX also declares that it needs to support that though, so I think it's a safe assumption to state that command -v will provide you with what you need. > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0done > =A0 =A0 =A0 =A0fi > } > > # die [ $err_msg ... ] > # > # Optionally print a message to stderr before exiting with failure status= . > # > : dependency checks performed after depend-function declaration > : function ; die ( ) # [ $err_msg ... ] > { > =A0 =A0 =A0 =A0local fmt=3D"$1" > =A0 =A0 =A0 =A0[ $# -gt 0 ] && shift 1 > =A0 =A0 =A0 =A0[ =A0"$fmt" =A0] && eprintf "$fmt\n" "$@" "x$fmt" !=3D x ? It seems like it could be simplified to: if [ $# -gt 0 ] then local fmt=3D$1 shift 1 eprintf "$fmt\n" "$@" fi > =A0 =A0 =A0 =A0show_deps > =A0 =A0 =A0 =A0exit ${FAILURE-1} > } > > # have $anything > # > # Used for dependency calculations. Arguments are passed to the `type' bu= ilt-in > # to determine if a given command is available (either as a shell built-i= n or > # as an external binary). The return status is true if the given argument= is > # for an existing built-in or executable, otherwise false. > # > # This is a convenient method for building dependency lists and it aids i= n the > # readability of a script. For example, > # > # =A0 =A0 =A0 Example 1: have sed || die "sed is missing" > # =A0 =A0 =A0 Example 2: if have awk; then > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 # We have awk... > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0else > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 # We DON'T have awk... > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > # =A0 =A0 =A0 Example 3: have reboot && reboot > # > : dependency checks performed after depend-function declaration > : function ; have ( ) # $anything > { > =A0 =A0 =A0 =A0type "$@" > /dev/null 2>&1 > } > > # depend $name [ $dependency ... ] > # > # Add a dependency. Die with error if dependency is not met. > # > : dependency checks performed after depend-function declaration > : function ; depend ( ) # $name [ $dependency ... ] > { > =A0 =A0 =A0 =A0local by=3D"$1" arg > =A0 =A0 =A0 =A0shift 1 > > =A0 =A0 =A0 =A0for arg in "$@"; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0local d Wouldn't it be better to declare this outside of the loop (I'm not sure how optimal it is to place it inside the loop)? > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0for d in $_depend ""; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0[ "$d" =3D "$arg" ] && bre= ak > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0done > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if [ ! "$d" ]; then Could you make this ` "x$d" =3D x ' instead? > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0have "$arg" || die \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"%s: Missi= ng dependency '%s' required by %s" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"${prognam= e:-$0}" "$arg" "$by" The $0 substitution is unnecessary based on how you set progname above: $ foo=3Dyadda $ echo ${foo##*/} yadda $ foo=3Dyadda/badda/bing/bang $ echo ${foo##*/} bang > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0_depend=3D"$_depend${_depe= nd:+ }$arg" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > =A0 =A0 =A0 =A0done > } > > # > # Perform dependency calculations for above rudimentary functions. > # NOTE: Beyond this point, use the depend-function BEFORE dependency-use > # > depend fprintf =A0 'local' '[' 'return' 'shift' 'printf' > depend eprintf =A0 'fprintf' > depend show_deps 'if' '[' 'then' 'eprintf' 'local' 'for' 'do' 'done' 'fi' > depend die =A0 =A0 =A0 'local' '[' 'shift' 'eprintf' 'show_deps' 'exit' > depend have =A0 =A0 =A0'local' 'type' 'return' > depend depend =A0 =A0'local' 'shift' 'for' 'do' '[' 'break' 'done' 'if' '= then' \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 'have' 'die' 'fi' I'd say that you have bigger fish to try if your shell lacks the needed lexicon to parse built-ins like for, do, local, etc :)... > # usage > # > # Prints a short syntax statement and exits. > # > depend usage 'local' 'eprintf' 'die' > : function ; usage ( ) # > { > =A0 =A0 =A0 =A0local optfmt=3D"\t%-12s%s\n" > =A0 =A0 =A0 =A0local envfmt=3D"\t%-22s%s\n" > > =A0 =A0 =A0 =A0eprintf "Usage: %s [OPTIONS] name[=3Dvalue] ...\n" "${prog= name:-$0}" > > =A0 =A0 =A0 =A0eprintf "OPTIONS:\n" > =A0 =A0 =A0 =A0eprintf "$optfmt" "-h --help" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Print this message to stderr and exit." > =A0 =A0 =A0 =A0eprintf "$optfmt" "-d" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Print list of internal dependencies befor= e exit." > =A0 =A0 =A0 =A0eprintf "$optfmt" "-e" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Print query results as \`var=3Dvalue' (us= eful for producing" > =A0 =A0 =A0 =A0eprintf "$optfmt" "" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"output to be fed back in). Ignored if -n = is specified." > =A0 =A0 =A0 =A0eprintf "$optfmt" "-n" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Show only variable values, not their name= s." > =A0 =A0 =A0 =A0eprintf "\n" > > =A0 =A0 =A0 =A0eprintf "ENVIRONMENT:\n" > =A0 =A0 =A0 =A0eprintf "$envfmt" "SYSRC_SHOW_DEPS" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Dump list of dependencies. Must be zero o= r one" > =A0 =A0 =A0 =A0eprintf "$envfmt" "" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"(default: \`0')" > =A0 =A0 =A0 =A0eprintf "$envfmt" "RC_DEFAULTS" \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Location of \`/etc/defaults/rc.conf' file= ." > > =A0 =A0 =A0 =A0die > } > > # sysrc $setting > # > # Get a system configuration setting from the collection of system- > # configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf > # and /etc/rc.conf). > # > # Examples: > # > # =A0 =A0 =A0 sysrc sshd_enable > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns YES or NO > # =A0 =A0 =A0 sysrc defaultrouter > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns IP address of default router (if co= nfigured) > # =A0 =A0 =A0 sysrc 'hostname%%.*' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns $hostname up to (but not including)= first `.' > # =A0 =A0 =A0 sysrc 'network_interfaces%%[$IFS]*' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns first word of $network_interfaces > # =A0 =A0 =A0 sysrc 'ntpdate_flags##*[$IFS]' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns last word of $ntpdate_flags (time s= erver address) > # =A0 =A0 =A0 sysrc usbd_flags-"default" > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns $usbd_flags or "default" if unset > # =A0 =A0 =A0 sysrc usbd_flags:-"default" > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns $usbd_flags or "default" if unset o= r NULL > # =A0 =A0 =A0 sysrc cloned_interfaces+"alternate" > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns "alternate" if $cloned_interfaces i= s set > # =A0 =A0 =A0 sysrc cloned_interfaces:+"alternate" > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns "alternate" if $cloned_interfaces i= s set and non-NULL > # =A0 =A0 =A0 sysrc '#kern_securelevel' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns length in characters of $kern_secur= elevel > # =A0 =A0 =A0 sysrc 'hostname?' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns NULL and error status 2 if $hostnam= e is unset (or if > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 set, returns the value of $hostname with no= error status) > # =A0 =A0 =A0 sysrc 'hostname:?' > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 returns NULL and error status 2 if $hostnam= e is unset or NULL > # =A0 =A0 =A0 =A0 =A0 =A0 =A0 (or if set and non-NULL, returns value with= out error status) > # I would probably just point someone to a shell manual, as available options and behavior may change, and behavior shouldn't (but potentially could) vary between versions of FreeBSD. > depend sysrc 'local' '[' 'return' '.' 'have' 'eval' 'echo' > : function ; sysrc ( ) # $varname > { > =A0 =A0 =A0 =A0: ${RC_DEFAULTS:=3D"/etc/defaults/rc.conf"} > > =A0 =A0 =A0 =A0local defaults=3D"$RC_DEFAULTS" > =A0 =A0 =A0 =A0local varname=3D"$1" > > =A0 =A0 =A0 =A0# Check arguments > =A0 =A0 =A0 =A0[ -r "$defaults" ] || return > =A0 =A0 =A0 =A0[ "$varname" ] || return > > =A0 =A0 =A0 =A0( # Execute within sub-shell to protect parent environment > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0[ -f "$defaults" -a -r "$defaults" ] && . = "$defaults" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0have source_rc_confs && source_rc_confs > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0eval echo '"${'"$varname"'}"' 2> /dev/null > =A0 =A0 =A0 =A0) > } > > # ... | lrev > # lrev $file ... > # > # Reverse lines of input. Unlike rev(1) which reverses the ordering of > # characters on a single line, this function instead reverses the line > # sequencing. > # > # For example, the following input: > # > # =A0 =A0 =A0 Line 1 > # =A0 =A0 =A0 Line 2 > # =A0 =A0 =A0 Line 3 > # > # Becomes reversed in the following manner: > # > # =A0 =A0 =A0 Line 3 > # =A0 =A0 =A0 Line 2 > # =A0 =A0 =A0 Line 1 > # > depend lrev 'local' 'if' '[' 'then' 'while' 'do' 'shift' 'done' 'else' 'r= ead' \ > =A0 =A0 =A0 =A0 =A0 =A0'fi' 'echo' > : function ; lrev ( ) # $file ... > { > =A0 =A0 =A0 =A0local stdin_rev=3D > =A0 =A0 =A0 =A0if [ $# -gt 0 ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# Reverse lines from files passed as posit= ional arguments. > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0while [ $# -gt 0 ]; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0local file=3D"$1" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0[ -f "$file" ] && lrev < "= $file" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0shift 1 > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0done > =A0 =A0 =A0 =A0else > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# Reverse lines from standard input > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0while read -r LINE; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0stdin_rev=3D"$LINE > $stdin_rev" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0done > =A0 =A0 =A0 =A0fi > > =A0 =A0 =A0 =A0echo -n "$stdin_rev" > } > > # sysrc_set $setting $new_value > # > # Change a setting in the system configuration files (edits the files in-= place > # to change the value in the last assignment to the variable). If the var= iable > # does not appear in the source file, it is appended to the end of the pr= imary > # system configuration file `/etc/rc.conf'. > # > depend sysrc_set 'local' 'sysrc' '[' 'return' 'for' 'do' 'done' 'if' 'hav= e' \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 'then' 'else' 'while' 'read' 'case' 'esac= ' 'fi' 'break' \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 'eprintf' 'echo' 'lrev' > : function ; sysrc_set ( ) # $varname $new_value > { > =A0 =A0 =A0 =A0local rc_conf_files=3D"$( sysrc rc_conf_files )" > =A0 =A0 =A0 =A0local varname=3D"$1" new_value=3D"$2" IIRC I've run into issues doing something similar to this in the past, so I broke up the local declarations on 2+ lines. > =A0 =A0 =A0 =A0local file conf_files=3D > > =A0 =A0 =A0 =A0# Check arguments > =A0 =A0 =A0 =A0[ "$rc_conf_files" ] || return ${FAILURE-1} > =A0 =A0 =A0 =A0[ "$varname" ] || return ${FAILURE-1} > Why not just do... if [ "x$rc_conf_files" =3D x -o "x$varname" =3D x ] then return ${FAILURE-1} fi ...? > =A0 =A0 =A0 =A0# Reverse the order of files in rc_conf_files > =A0 =A0 =A0 =A0for file in $rc_conf_files; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0conf_files=3D"$file${conf_files:+ }$conf_f= iles" > =A0 =A0 =A0 =A0done > > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0# Determine which file we are to operate on. If no files m= atch, we'll > =A0 =A0 =A0 =A0# simply append to the last file in the list (`/etc/rc.con= f'). > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0local found=3D > =A0 =A0 =A0 =A0local regex=3D"^[[:space:]]*$varname=3D" > =A0 =A0 =A0 =A0for file in $conf_files; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0#if have grep; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if false; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0grep -q "$regex" $file && = found=3D1 Probably want to redirect stderr for the grep output to /dev/null, or test for the file's existence first, because rc_conf_files doesn't check for whether or not the file exists which would result in noise from your script: $ . /etc/defaults/rc.conf $ echo $rc_conf_files /etc/rc.conf /etc/rc.conf.local $ grep -q foo /etc/rc.local grep: /etc/rc.local: No such file or directory > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0else > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0while read LINE; do \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0case "$LIN= E" in \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0$varname= =3D*) found=3D1;; \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0esac; \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0done < $file > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0[ "$found" ] && break > =A0 =A0 =A0 =A0done > > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0# Perform sanity checks. > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0if [ ! -w $file ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0eprintf "\n%s: cannot create %s: permissio= n denied\n" \ Being pedantic, I would capitalize the P in permission to match EACCES's output string. > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"${progname:-$0}" "$file" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return ${FAILURE-1} > =A0 =A0 =A0 =A0fi > > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0# If not found, append new value to last file and return. > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0if [ ! "$found" ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo "$varname=3D\"$new_value\"" >> $file > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return ${SUCCESS-0} > =A0 =A0 =A0 =A0fi > > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0# Operate on the matching file, replacing only the last oc= currence. > =A0 =A0 =A0 =A0# > =A0 =A0 =A0 =A0local new_contents=3D"`lrev $file 2> /dev/null | \ > =A0 =A0 =A0 =A0( found=3D > =A0 =A0 =A0 =A0 =A0while read -r LINE; do > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if [ ! "$found" ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0#if have grep; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if false; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0match=3D"$= ( echo "$LINE" | grep "$regex" )" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0else > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0case "$LIN= E" in > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0$varname= =3D*) match=3D1;; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 = =A0 =A0 *) match=3D;; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0esac > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if [ "$match" ]; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0LINE=3D"$v= arname"'=3D"'"$new_value"'"' > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0found=3D1 > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo "$LINE" > =A0 =A0 =A0 =A0 =A0done > =A0 =A0 =A0 =A0) | lrev`" > > =A0 =A0 =A0 =A0[ "$new_contents" ] \ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0&& echo "$new_contents" > $file What if this write fails, or worse, 2+ people were modifying the file using different means at the same time? You could potentially lose/corrupt your data and your system is potentially hosed, is it not? Why not write the contents out to a [sort of?] temporary file (even $progname.$$ would suffice probably, but that would have potential security implications so mktemp(1) might be the way to go), then move the temporary file to $file? You might also want to use lockf to lock the file. > } > > ############################################################ MAIN SOURCE > > # > # Perform sanity checks > # > depend main '[' 'usage' > [ $# -gt 0 ] || usage > > # > # Process command-line options > # > depend main 'while' '[' 'do' 'case' 'usage' 'eprintf' \ > =A0 =A0 =A0 =A0 =A0 =A0'break' 'esac' 'shift' 'done' > while [ $# -gt 0 ]; do Why not just use the getopts shell built-in and shift $(( $OPTIND - 1 )) at the end? > =A0 =A0 =A0 =A0case "$1" in > =A0 =A0 =A0 =A0-h|--help) usage;; > =A0 =A0 =A0 =A0-d) SYSRC_SHOW_DEPS=3D1;; > =A0 =A0 =A0 =A0-e) SHOW_EQUALS=3D1;; > =A0 =A0 =A0 =A0-n) SHOW_NAME=3D;; > =A0 =A0 =A0 =A0-*) eprintf "%s: unrecognized option \`$1'\n" "${progname:= -$0}" > =A0 =A0 =A0 =A0 =A0 =A0usage;; > =A0 =A0 =A0 =A0 *) # Since it is impossible (in many shells, including bo= urne, c, > =A0 =A0 =A0 =A0 =A0 =A0# tennex-c, and bourne-again) to name a variable b= eginning with a > =A0 =A0 =A0 =A0 =A0 =A0# dash/hyphen [-], we will terminate the option-li= st at the first > =A0 =A0 =A0 =A0 =A0 =A0# item that doesn't begin with a dash. > =A0 =A0 =A0 =A0 =A0 =A0break;; > =A0 =A0 =A0 =A0esac > =A0 =A0 =A0 =A0shift 1 > done > [ "$SHOW_NAME" ] || SHOW_EQUALS=3D > > # > # Process command-line arguments > # > depend main '[' 'while' 'do' 'case' 'echo' 'sysrc' 'if' 'sysrc_set' 'then= ' \ > =A0 =A0 =A0 =A0 =A0 =A0'fi' 'esac' 'shift' 'done' > SEP=3D': ' > [ "$SHOW_EQUALS" ] && SEP=3D'=3D"' > while [ $# -gt 0 ]; do > =A0 =A0 =A0 =A0NAME=3D"${1%%=3D*}" > =A0 =A0 =A0 =A0case "$1" in > =A0 =A0 =A0 =A0*=3D*) > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo -n "${SHOW_NAME:+$NAME$SEP}$( > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 sysrc "$1" )${SHOW_EQUALS= :+\"}" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if sysrc_set "$NAME" "${1#*=3D}"; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo " -> $( sysrc "$NAME"= )" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0fi What happens if this set fails :)? It would be confusing to end users if you print out the value (and they expected it to be set), but it failed for some particular reason. > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0;; > =A0 =A0 =A0 =A0*) > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if ! IGNORED=3D"$( sysrc "$NAME?" )"; then > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo "${progname:-$0}: unk= nown variable '$NAME'" > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0else > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0echo "${SHOW_NAME:+$NAME$S= EP}$( > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0sysrc "$1" )${= SHOW_EQUALS:+\"}" Not sure if it's a gmail screwup or not, but is there supposed to be a newline between `$(' and `sysrc' ? And now some more important questions: 1. What if I do: sysrc PS1 :) (hint: variables inherited from the shell really shouldn't end up in the output / be queried)? 2. Could you add an analog for sysctl -a and sysctl -n ? 3. There are some more complicated scenarios that unfortunately this might not pass when setting variables (concerns that come to mind deal with user-set $rc_conf_files where values could be spread out amongst different rc.conf's, and where more complicated shell syntax would become a slippery slope for this utility, because one of the lesser used features within rc.conf is that it's nothing more than sourceable bourne shell script :)...). I would definitely test the following scenarios: #/etc/rc.conf-1: foo=3Dbaz #/etc/rc.conf-2: foo=3Dbar #/etc/rc.conf-3: foo=3D"$foo zanzibar" Scenario A: #/etc/rc.conf: rc_conf_files=3D"/etc/rc.conf-1 /etc/rc.conf-2" The value of foo should be set to bar; ideally the value of foo in /etc/rc.conf-2 should be set to a new value by the end user. Scenario B: #/etc/rc.conf: rc_conf_files=3D"/etc/rc.conf-2 /etc/rc.conf-1" The value of foo should be set to baz; ideally the value of foo in /etc/rc.conf-1 should be set to a new value by the end user. Scenario C: #/etc/rc.conf: rc_conf_files=3D"/etc/rc.conf-1 /etc/rc.conf-2 /etc/rc.conf-3" The value of foo should be set to `bar zanzibar'; ideally the value of foo in /etc/rc.conf-3 should be set to a new value by the end user (but that will affect the expected output potentially). Scenario D: #/etc/rc.conf: rc_conf_files=3D"/etc/rc.conf-2 /etc/rc.conf-1 /etc/rc.conf-3" The value of foo should be set to `baz zanzibar'; ideally the value of foo in /etc/rc.conf-3 should be set to a new value by the end user (but that will affect the expected output potentially). I'll probably think up some more scenarios later that should be tested... the easy way out is to state that the tool does a best effort at overwriting the last evaluated value. Overall, awesome looking tool and I'll be happy to test it Thanks! -Garrett