Bash: how to unset an env variable with a hyphen (-)? - bash

I have in my environment some variables that have an invalid identifier. When I try to unset them, I get the following error:
$ unset A-B
bash: unset: `A-B': not a valid identifier
How to unset them?

Related to the answer by Honza P:
According to man bash the environment variables have the form name=value where name consists only of alphanumeric characters and underscores, and begins with an alphabetic character or an underscore. So the name A-B is an invalid variable identifier for Bash, and cannot be used or accessed because it has internal validations (enforced strongly in late Bash versions because of vulnerabilities in Bash itself). The only solution is starting another secondary sub-shell that not have the offending names using the utility env to erasing them before launching Bash:
env -u 'A-B' bash
Note that the single quotes aren't needed, but to me is more readable that way as indicates the text inside is a string, not other command.
If you have more variables simply list them separated by spaces, or if you want to run only an script without the variables you can use for example:
env -u 'A-B' 'OTHER-VAR' 'bad-name' bash -c 'myscript.sh arg1 arg2'
The subshell will run myscript.sh with the arguments arg1 and arg2, and exit to the current Bash shell.
Consult the man page of 'env': man env

Not the best solution I guess but it worked in my case.
I tried an example by bishop env -u "foo-bar=baz", then env -u "foo-bar" but this approach still left some garbage in variables e.g. _=foo-bar. So I used unset _ and then it is gone.

Related

Why are quotes preserved when using bash $() syntax, but not if executed manually?

I have the following bash script:
$ echo $(dotnet run --project Updater)
UPDATE_NEEDED='0' MD5_SUM="7e3ad68397421276a205ac5810063e0a"
$ export UPDATE_NEEDED='0' MD5_SUM="7e3ad68397421276a205ac5810063e0a"
$ echo $UPDATE_NEEDED
0
$ export $(dotnet run --project Updater)
$ echo $UPDATE_NEEDED
'0'
Why is it $UPDATE_NEEDED is 0 on the 3rd command, but '0' on the 5th command?
What would I need to do to get it to simply set 0? Using UPDATE_NEEDED=0 instead is not an option, as some of the other variables may contain a space (And I'd like to optimistically quote them to have it properly parse spaces).
Also, this is a bit of a XY problem. If anyone knows an easier way to export multiple variables from an executable that can be used later on in the bash script, that could also be useful.
To expand on the answer by Glenn:
When you write something like export UPDATE_NEEDED='0' in Bash code, this is 100% identical to export UPDATE_NEEDED=0. The quotes are used by Bash to parse the command expression, but they are then discarded immediately. Their only purpose is to prevent word splitting and to avoid having to escape special characters. In the same vein, the code fragment 'foo bar' is exactly identical to foo\ bar as far as Bash is concerned: both lead to space being treated as literal rather than as a word splitter.
Conversely, parameter expansion and command substitution follows different rules, and preserves literal quotes.
When you use eval, the command line arguments passed to eval are treated as if they were Bash code, and thus follow the same rules of expansion as regular Bash code, which leads to the same result as (1).
Apparently that Updater project is doing the equivalent of
echo "UPDATE_NEEDED=\'0\' MD5_SUM=\"7e3ad68397421276a205ac5810063e0a\""
It's explicitly outputting the quotes.
When you do export UPDATE_NEEDED='0' MD5_SUM="7e3ad68397421276a205ac5810063e0a",
bash will eventually remove the quotes before actually setting the variables.
I agree with #pynexj, eval is warranted here, although additional quoting is recommended:
eval export "$(dotnet ...)"

why creating var in bash need no space?

I tried creating a var to do some operation in k8s.
I intentionally put space between '=' and export and my cmd I want to shortcut.
Why is linux shell giving me error?
export do = '--dry-run=client -o yaml'
-bash: export: `=': not a valid identifier
-bash: export: `--dry-run=client -o yaml': not a valid identifier
The basic operation in shell is running a command. You do this by stating the name of the command, followed by an optional space-separated list of arguments. Every other bit of syntax follows from this.
export do = '--dry-run=client -o yaml' is just a call to the command export with 3 separate arguments. There is no assignment.
An assignment is a single word containing a single = that appears in an appropriate context. In the case of an export command, each of its argument is either:
A valid identifier, on which the export attribute is set
A valid assignment in the form name=value, in which case name receives the export attribute and the value value is assigned to the name.
As mentioned in the comments, it's better to use an array to store lists of arguments. (Note that you cannot export an array, nor do you likely need to export any name that's only going to be used in the shell, rather than looked for in the environment of a child process.)
do=(--dry-run=client -o yaml)
some_command "${do[#]}" # instead of some_command $do
The reason why this doesn't work has to do with backwards-compliance for bash. The syntax you use on the CLI is the same on your would use on a shell script; shell scripts will always take the first word on each line and interpret them as commands.
Everything separated by a space after the command name will be interpreted as a space-separated list of arguments. Let's look at a simpler version of your export, as it's essentially setting your defined variable as an environment variable:
# This will evaluate to the command 'do' being executed with '='
# and '--dry-run=client -o yaml' being evaluated as arguments
do = '--dry-run=client -o yaml'
In order to circumvent that, Bash needs your variable assignments to be made without spaces. This way bash can lookup for any equal signs in your command and interpret them as assignments instead.
# Assigns the string '--dry-run=client -o ymal' to the variable 'do'
do='--dry-run=client -o yaml'
Since export just takes a variable assignment and turns it into a global environment variable, you need to follow the same convention:
export do='--dry-run=client -o yaml'
Export works like every unix program:
program agr1 arg2
The args are separated via spaces, so in your case the args are:
arg1: "do"
arg2: "="
arg3: "'--dry-run=client -o yaml'"
and export can't handle this, because it is meaningless.
But if you try this:
export a='echo a' b='echo b'
the args will be:
arg1: "a='echo a'"
arg2: "b='echo b'"
and export knows this syntax.

bash shell access to $ProgramFiles(x86) environment variable

In the bash shell, I'm using git-bash.exe, how do I access the Windows 10 ProgramFiles(x86) environment variable?
If I execute printenv I see it in the output with the casing noted but attempts to access it using echo $ProgramFiles(x86), echo $ProgramFiles\(x86\) and echo $"ProgramFiles(x86)" do not work.
I am able to access the non-x86 version of that environment variable without any issue using echo $PROGRAMFILES and do relevant colon removal and backslash to forward replacements necessary to use it in PATH environment variable updates, e.g. PATH=$PATH:"/${PROGRAMFILES//:\\//} (x86)/Some App Path With Spaces/" followed by echo $PATH and printenv PATH that confirms the desired result. The issue is that I'd rather not have to compose the ProgramFiles(x86) environment variable versus being able to use it directly in updates to the PATH environment variable.
Along these same lines when trying to use the Windows APPDATA [ = C:\Users<username>\AppData\Roaming ] environment variable in updates to PATH environment variable I need to be able to replace not only the initial colon & backslash but also the subsequent backslashes with forward slashes. Using echo ${APPDATA//:\\//} produces C/Users\<username>\AppData\Roaming and I'm not aware of how to get the bash environment variable character matching and substitution syntax to cover both cases in order to produce the required C/Users/<username>/AppData/Roaming necessary for use in updates to PATH environment variable.
Note: there's a flaw in the process described below. In particular, if some environment variable is set to a multi-line value where one of the value lines matches the sed expression, you'll capture that line as well. To avoid this, if you have a Python available, you could use:
python -c 'import os; print(os.getenv("FOO(BAR)"))'
for instance. This will print None if the variable is not set, so you might want to make it fancier: e.g., supply a default value, or use sys.exit(1) if the variable is not set, for instance. (But if you have a Python interpreter available, you might consider writing in Python rather than directly in bash.)
Unix shell (sh, bash, etc) variable names—including environment variables—are constrained to character sets that exclude parentheses. In particular, "$FOO(BAR)" always parses as a reference to variable $FOO, followed by (BAR) as a separate word. This holds even with braceed expansion forms, where the separate word (BAR) is syntactically invalid:
bash$ echo "${FOO(BAR)}"
bash: ${FOO(BAR)}: bad substitution
Nonetheless, it is possible to set such variables, and access them, using other programs. For instance, using Python I set FOO(BAR) to hello:
>>> import os
>>> os.environ["FOO(BAR)"] = "hello"
>>> import subprocess
>>> subprocess.call("bash")
bash$
This bash instance cannot directly access the variable, but env prints all the variables:
bash$ env | grep FOO
FOO(BAR)=hello
If you have env (you probably do) and sed, you can combine them to extract arbitrary variables:
bash$ setting="$(env | sed -n 's/^FOO(BAR)=//p')"
bash$ echo "$setting"
hello
So assuming that Windows Bash doesn't have any special case to work around this particular clumsiness better, this same trick should work for "ProgramFiles(x86)".
Substituting multiple backslashes with forward slashes
You're mostly there: the problem is that your pattern looks specifically for :\ but the strings have multiple \s without colons. Your best bet is probably to have a program or function that actually understands Windows paths, as they don't necessarily have drive letters at the front (see https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats). But this pattern works for all-backslash:
bash$ v='a\b\c'
bash$ echo ${v//\\/\/}
a/b/c
The double slash means "substitute all occurrences". The pattern is then \\, which matches one backslash. The next slash introduces the replacement string, which is \/, which means one forward slash. (This can also be written as just / but I find that harder to read, oddly enough.)
Of course this does not replace the colon in C:, so we need one more substitution. You can't do that in one ${...} expansion, so the trick is to add another one:
bash$ v='C:\a\b\c'
bash$ echo ${v//\\/\/}
C:/a/b/c
bash$ v1="${v//\\//}"; echo ${v1/:/}
C/a/b/c
Put this inside a shell function, which you can eventually make smart enough to handle all valid paths, and that way you can use local to keep the variable name v1 from leaking.
Regarding APPDATA: The cygpath program can convert pathnames between Windows, Unix and "Mixed" conventions. Both Cygwin and Git for Windows come with this tool. Example:
$ echo "$APPDATA"
C:\Users\me\AppDataRoaming\
$ cygpath -u "$APPDATA"
/c/Users/me/AppData/Roaming
$ cygpath -m "$APPDATA"
C:/Users/me/AppData/Roaming
$ cygpath -w "$APPDATA"
C:\Users\me\AppData\Roaming
The "mixed" format is quite usefull because even most windows programs and Git for Windows can handle that format directly.
Assigning the output of cygpath to a variable works like this (note the quotes!):
$ XAPP=$(cygpath "$APPDATA")
$ echo "$XAPP"
$ cd "$XAPP"

Variables in remote SSH session

I have some inconsistent behavior in my bash script.
There are variables, and all of them have values assigned to them, I confirm them by echoing the values at the beginning of the script.
However, when passing them to a remote SSH session, one variable has value, whereas the other one appear to be blank. I am pretty sure I am not overwriting the variable's value.
# Script input arguments
USER=$1
SERVER=$2
# Other vars
PFX=$8
#
ADDRESS=$USER#$SERVER
function run {
ssh $ADDRESS /bin/bash $#
}
# Script body, some stuff happens here
run << "SSHCONNECTION2"
sudo mv "/home/$USER/$PFX" "/home/$USER/certs/"
SSHCONNECTION2
So, the output of mv is
error 03-Jan-2017 17:20:39 mv: cannot move '/home/admin/' to a subdirectory of itself, '/home/admin/certs/admin'
Can somebody give me a hint what am I doing wrong?
Thank you.
USER had a remote value because USER always has a value: By default, it's the current user account on all POSIX systems. To avoid conflicting with system-defined variable names, you should use lower-case names for your own shell and environment variables (the former because setting a shell variable with a name that overlaps an environment variable will implicitly overwrite the latter).
#!/bin/bash
# ^^^^ - not /bin/sh; printf %q (thus, eval-safe quoting) is a bash extension
user=$1
pfx=$8
# Tell the shell to quote your variables to be eval-safe!
printf -v user_q '%q' "$user"
printf -v pfx_q '%q' "$pfx"
# ...then you can use those eval-safe version in your heredoc
run << SSHCONNECTION2
# because the values are self-quoted, don't put their expansions inside quotes
sudo mv /home/$user_q/$pfx_q /home/$user_q/certs/
SSHCONNECTION2
Notes:
The sigil (SSHCONNECTION2) is intentionally unquoted to allow expansions to occur.
Using lower-case variable names avoids inadvertently conflicting with names meaningful to the shell or system.
The above is a bit unfortunate, because the literal contents of the SSHCONNECTION2 heredoc isn't code that could safely be run directly in a shell. Consider this answer instead.

Run a bash including a variable in the "bash XXX.sh" command

I am completely new to "programming" in Linux, and I wonder if it is possible to include the definition of a variable when I run a bash file.
My bash file needs the variable in order to go from one or another path, so I would like to be able to include it when running the script.
Something like this:
bash MYFILE.sh -VARIABLE
So the -VARIABLE would be used in the script.
Thank you!
You can take advantage of shell parameter expansion to smoothly read variables from the environment of the parent process, if it's that what you want to achieve.
Look at the following script named test.sh:
#!/bin/bash
VARIABLE=${VARIABLE:="default value"}
echo $VARIABLE
If you start it with the line
$ ./test.sh
it outputs
default value
But if you invoke test.sh with the line
$ VARIABLE="custom Value" ./test.sh
it outputs
custom value
But make sure that the variable assignment is at the beginning of the line. Otherwise it is passed to test.sh as command line argument.
The used form of parameter expansion ${parameter:=word} is described in the bash reference manual as:
If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional parameters and special parameters may not be assigned to in this way.

Resources