Can I prevent echo from expanding env vars? - linux

I have an automatic setup script that is executed when a new user is created. It runs this line of code to set up the golang environment: echo "export PATH="$PATH:$GOPATH/bin"" >> ~/.profile
But this will expand all environment variables before writing into the file. Is there a way to write export PATH="$PATH:$GOPATH/bin" into a file from the command line without expanding the environment variables?

Try:
echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.profile
Single-quoted strings in POSIX-like shells (such as bash) treat their content as literals, which is what you want here.
The only reason to use a double-quoted string here would be to selectively expand variable references up front - which doesn't apply in your case.
That said, here's an example:
$ echo "Honey, I'm \"$USER\" and I'm \$HOME."
Honey, I'm "jdoe" and I'm $HOME.
Backslash-escaping is used to escape embedded " and $ instances that should be treated as literals.
As for what you tried:
"export PATH="$PATH:$GOPATH/bin""
is actually a string concatentation, composed of 3 separate strings:
"export PATH=", which, as a double-quoted string that happens not to contains $-prefixed interpolation elements, expands to literal export PATH=
$PATH:$GOPATH/bin, which, as an unquoted string, is subject to additional shell expansions, which not only involves expanding variables $PATH and $GOPATH to their respective values, but also applies word-splitting and pathname expansion (globbing).
"", which amounts to the empty string and is effectively ignored.
Note how POSIX-like shells allow you to compose larger strings (concatenate strings) by placing strings - unquoted or single-quoted or double-quoted - directly next to one another.

Related

Bash prompt variables does not work inside a bash function

I'm working on a prompt customization, but for some reason, when I use the \u, \h and \W variables as is it works perfectly, but when I put them inside a function, they are displayed as "\u" or "\W" instead of their values.
...
print_user()
{
echo -e "\001\u\002#\001\h\002"
}
print_dir()
{
echo -e "\001${YELLOW}\002\001\W\002\001${RESET_ATTR}\002"
}
PS1='[$(print_user) on $(print_dir)] $(get_git_repo) \001\n\002$(print_prompt) '
This displays as:
[\u#\h on \W]
>
If I move them outside of the function like so
PS1='[\[\u\]#\[\h\] \[${YELLOW}\]\[\w\]\[${RESET_ATTR}\]] $(get_git_repo) \[\n\]$(print_prompt)'
It works fine, and displays the current directory with the username and hostname:
[myusername#arch on ~]
>
Is this just how bash works? Is there a different way of doing it so it will work? Why is it that inside of a function it won't display the variables' values but outside of a function it does?
From the man page, under PROMPTING
Bash allows these prompt strings to be customized by inserting a number of
backslash-escaped special characters that are decoded as follows:
[...]
After the string is decoded, it is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal, subject to the value of the
promptvars shell option (see the description of the shopt command under SHELL BUILTIN COMMANDS below).
By the time the shell expands $(print_user) to add \u to the string, it is too late to decode it, so the literal string \u remains in the prompt.
One alternative is to use PROMPT_COMMAND to execute a function that defines PS1 dynamically, just before it is displayed, instead of embedding command substitution in the value of PS1 itself.
make_prompt () {
PS1="[$(print_user) on $(print_dir)] $(get_git_repo)"
PS1+='\[\n\]'
PS1+="$(print_prompt) "
}
PROMPT_COMMAND=make_prompt
Now, print_user will have been called before the shell decodes the value of PS1, by which time all the prompt escapes will be present.

Bash don't replace $ [duplicate]

This question already has answers here:
Difference between single and double quotes in Bash
(7 answers)
Closed 1 year ago.
I want to write a bash script which creates other bash scripts. But when I do
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH" >> NEWFILE.sh
it already replaces $LD_LIBRARY_PATH in the first script.
So in NEWFILE.sh I only get:
export LD_LIBRARY_PATH=:~/SOME/PATH
But i want that in the NEWFILE.sh there's still:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH
So it gets replaced, when running NEWFILE.sh. I hope this makes sense. Thank you for your help
If you don't want the variable interpolated, use single quotes:
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH' >> NEWFILE.sh
But even this small snippet has issues. When NEWFILE.sh runs with an empty LD_LIBRARY_PATH, it will create an invalid value that has a leading :. Also, it is bad practice to use ~ in variables. Instead, you should do:
echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}$HOME/SOME/PATH' >> NEWFILE.sh
Also, this snippet makes NEWFILE.sh not idempotent, and you may wind up with multiple instances of $HOME/SOME/PATH in the final value. This is easy enough to avoid, but takes a bit more logic. Something like:
cat << \EOF >> NEWFILE.sh
pathmunge () { case ":${LD_LIBRARY_PATH}:" in *:"$1":*) ;;
*) LD_LIBRARY_PATH="$LD_LIBRARY_PATH${LD_LIBRARY_PATH:+:}$1";; esac; }
pathmunge $HOME/SOME/PATH
export LD_LIBRARY_PATH
EOF
This uses a common technique of quoting the HEREDOC (the backslash before the EOF is essential; see how it changes when you remove the leading backslash) to prevent interpolation, and allows multi-line output and quotes to be used in the content that is going to be written to NEWFILE.sh.
One small point is that this will wind up putting the expansion of $HOME in LD_LIBRARY_PATH, which is probably what you want. If you really do want $HOME in the path (so that it is expanded when LD_LIBRARY_PATH is used, rather than when it is defined), you could do pathmunge '$HOME/path', but you may wind up with duplicate instances in the final value, since pathmunge will not recognize the unexpanded value as matching the expanded value. Avoiding that duplication is an exercise left for the reader.
Note that, depending on the remaining content of NEWFILE.sh, you may want to ensure that pathmunge is only defined once, and that this definition is not overriding some other definition. pathmunge is a common name for a function used for modifying PATH so you may want to consider a different name, or adding logic to allow it to take the name of the variable to be overridden.
You should escape the $ as follow:
echo "export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:~/SOME/PATH" >> NEWFILE.sh

Hash ("#") symbol in /etc/environment causes string to be split

I'm trying to add an environment variable to my system via
sudo nano /etc/environment
The value is a long string containing a hash, #.
With the # included, the string is not stored fully; characters after the # are gone.
Without the # included, the string is stored fully.
I have tried to wrap the string in " ":
MY_VARIABLE="34534554345 # DFGDGDFG"
I expect the variable to be stored fully, like this:
34534554345#DFGDGDFG
Not this:
34534554345
PAM interprets /etc/environment, not a shell. It's intended to be simple KEY=VALUE on each line with no need for quotes. # marks a comment and there is no way to escape it.
You can use /etc/profile to define your environment variable. It should make it available system wide in most cases.
/etc/environment
TEST2="12345#6789"
/etc/profile
export TEST="12345 #6789"
Result:
root#tempmon:~ $ env|grep TEST
TEST=12345# 6789
TEST2=12345

Passing \* as a parameter for a parameter

Using ksh. Trying to reuse a current script without modifying it, which basically boils down to something like this:
`expr 5 $1 $2`
How do i pass in a a multiplication command (*) as parameter $1 ?
I first attempted using "*" and even \* but that isn't working.
I've tried multiple escape backslash and quote combinations but i think im doing it wrong.
Without modifying the script, I don't think this can be done:
On calling, you can pass a literal * as '*', "*" or \* (any will do): this will initially protect the * from shell expansions (interpretation by the shell).
The callee (the script) will then receive literal * (as $1), but due to unquoted use of $1 inside the script, * will invariably be subject to filename expansion (globbing), and will expand to all (non-hidden) filenames in the current folder, breaking the expr command.
Trying to add an extra layer of escaping - such as "'*'" or \\\* - will NOT work, because the extra escaping will become an embedded, literal part of the argument - the target script will see literal '*' or \* and pass it as-is to expr, which will fail, because neither is a valid operator.
Here's a workaround:
Change to an empty directory.
By default, ksh will return any glob (pattern) as-is if there are no matching filenames. Thus, * (or any other glob) will be left unmodified in an empty directory, because there's nothing to match (thanks, #Peter Cordes).
For the calling script / interactive shell, you could disable globbing altogether by running set -f, but note that this will not affect the called script.
It's now safe to invoke your script with '*' (or any other glob), because it will simply be passed through; e.g., script '*' 2, will now yield 10, as expected
If both the shell you invoke from and the script's shell are ksh (or bash) with their default configuration, you can even get away with script * 2; i.e., you can make do without quoting * altogether.
Glob expansion happens very late, after parameter expansion, and word-splitting (in that order). Quote-removal doesn't happen on the results of earlier expansions, just what was on the command line to start with. This rules out passing in a quoted \* or similar (see mklement0's answer), by using an extra layer of quoting.
It also rules out passing in space-padded *: Word-splitting removes the spaces before pathname (glob) expansion, so it still ends up expanding * to all the filenames in the directory.
foo(){ printf '"%s"\n' "$#"; set -x; expr 5 $1 $2; set +x; }
$ foo ' * ' 4
" * "
"4"
+ expr 5 ...contents of my directory... 4
expr: syntax error
+ set +x
You should fix this buggy script before someone runs it with an arg that breaks it in a dangerous way, rather than just inconvenient.
If you don't need to support exactly the same operators as expr, you might want to use arithmetic expansion to do it without running an external command:
result=$((5 $1 $2)) # arithmetic expansion for the right-hand side
# or
((result=5 "$1" "$2")) # whole command is an arithmetic expression.
Double-quotes around parameters are optional inside an arithmetic expression, but you need to not use them in an arithmetic expansion (in bash. Apparently this works in ksh).
Normally it's not a bad habit to just always quote unless you specifically want word-splitting and glob expansion.

Why doesnt command execute successfully if i use variables?

I have the following script
WSO2_SCRIPT="JAVA_HOME=$JAVA_HOME /opt/autopilot/wso2is/bin/wso2server.sh"
WSO2_LOG="/var/log/autopilot/wso2is/autopilot-wso2is-initd.log"
${WSO2_SCRIPT} start >> ${WSO2_LOG} 2>&1 || echo failed
JAVA_HOME=$JAVA_HOME /opt/autopilot/wso2is/bin/wso2server.sh start >> /var/log/autopilot/wso2is/autopilot-wso2is-initd.log 2>&1 || echo failedagain
The third line of the code results in failure as I have "failed" echoed?
However the fourth line is successful and I don't get "failedagain" echoed.
Line 3 and 4 should result in exactly the same thing. Only difference is I am using variables in in line 3, and being explicit in line 4.
Why does using variables result in a failure?
When variables are expanded without quotes, they undergo word splitting and pathname expansion, but not shell grammar parsing.
This means that you can put the following in variables:
Multiple arguments (including command name) to be split up on spaces and spaces only
Globs like *.txt to be expanded
It also means that you can not put anything of the following in variables:
Redirections
Quotes
Pipes
Backgrounding &
Conditionals and loops, including if and [[ .. ]]
Brace and parenthesis groups
Parameter expansion
Command substitutions
Process substitutions
and as you've discovered: variable assignments
If you want to pass any of the above around as a variable, you should use a function and refer to the function instead. If you don't care about security and good practice, you can also use eval to evaluate a text string as shell code.

Resources