Re: [PATCH] t4014: shell portability fix

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, May 31, 2016 at 10:31:59PM -0400, Jeff King wrote:

> On Tue, May 31, 2016 at 08:09:43PM -0400, Eric Sunshine wrote:
> 
> > I was under the impression that the project was moving toward 'env' to
> > deal[1] with this sort of issue.
> > 
> > [1]: 512477b (tests: use "env" to run commands with temporary env-var
> > settings, 2014-03-18)
> 
> We can use it with test_must_fail, because it takes a command name:
> 
>   test_must_fail env FOO=BAR whatever-you-would-have-run
> 
> But I don't think it works in the general case, as test_commit does not
> run its arguments. So you'd want something like:
> 
>   env FOO=BAR test_commit exotic
> 
> but of course that doesn't work because "env" can't find the shell
> function. I think with bash you could do:
> 
>   export test_commit
>   env FOO=BAR bash -c test_commit exotic
> 
> but we can't rely on that.

I wondered if we could implement our own "env" in the shell, but it's a
little non-trivial, because:

  - our basic tool for setting variable-named variables is "eval", which
    means we need an extra layer of quoting

  - we have to restore the variables after. That means telling the
    difference between unset and empty (possible with "-" versus ":-", I
    think), but also the difference between unexported and exported
    (maybe possible by parsing "export -p", but I'd be shocked if that
    doesn't run into portability problems).

Here's what I came up with, which does seem to work. It's pretty gnarly,
though.

-- >8 --
# possible without a sub-program?
# portability issues with no-newline and sed?
shellquote () {
	printf "'"
	printf '%s' "$1" | sed "s/'/'\\\\''/g"
	printf "'"
}

# is there a simpler way, even when the contents are "unset"?
is_unset () {
	eval "test -z \"\${$1}\" && test unset = \"\${$1-unset}\""
}

# probably not portable; also, possible without sub-program?
is_exported () {
	export -p | grep "^declare -x $1="
}

# just a syntactic convenience
add_to () {
	eval "$1=\"\$$1
		\$2\""
}

fake_env () {
	fake_env_restore_=
	while test $# -gt 0
	do
		case "$1" in
		*=*)
			# this whole thing is not safe when the var name has
			# spaces or other meta-characters, but since the names
			# all come from our test scripts, that should be OK
			fake_env_var_=${1%%=*}
			eval "fake_env_orig_=\$$fake_env_var_"
			fake_env_val_=${1#*=}
			shift

			# unset value and clear export flag...
			add_to fake_env_restore_ "unset $fake_env_var_"
			# ...and then restore what was there, if anything
			if ! is_unset "$fake_env_var_"
			then
				add_to fake_env_restore_ \
					"$fake_env_var_=$(shellquote "$fake_env_orig_")"
				if is_exported "$fake_env_var_"
				then
					add_to fake_env_restore_ \
						"export $fake_env_var_"
				fi
			fi

			eval "$fake_env_var_=$(shellquote "$fake_env_val_")"
			eval "export $fake_env_var_"
			;;
		*)
			"$@"
			fake_env_ret_=$?
			eval "$fake_env_restore_"
			return $fake_env_ret_
			;;
		esac
	done
}

# simple exercise
show() {
	echo >&2 "$1: myvar=$myvar"
}

myvar="horrible \"\\' mess"
show before
fake_env myvar="temporary $myvar" show during
show after

-Peff
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]