Bash imported variables break configure script when double-quoted - bash

I have a bash script which imports same variables from another static file, which itself uses environment variables set by another file when the script is invoked.
This is the file that gets imported and sets some variables.
# package.mk
PKG_NAME="binutils"
PKG_VERSION="2.24"
PKG_URL_TYPE="http"
PKG_URL="http://ftp.gnu.org/gnu/binutils/${PKG_NAME}-${PKG_VERSION}.tar.bz2"
PKG_DEPENDS=""
PKG_SECTION="devel"
PKG_CONFIGURE_OPTS="--prefix=${TOOLS} \
--target=${TARGET} \
--with-sysroot=${TOOLS}/${TARGET} \
--disable-nls \
--disable-multilib"
It is used by the builds script as so:
#!/bin/bash
# Binutils
. settings/config
pkg_dir="$(locate_package 'binutils')"
. "${pkg_dir}/package.mk"
# etc...
"${CLFS_SOURCES}/${PKG_NAME}-${PKG_VERSION}/configure" "${PKG_CONFIGURE_OPTS}"
# etc...
This script first imports the settings/config file which has a bunch of global variables used by this script and others, and exports them so they are available as environment variables. It then locates the correct package.mk file for the specific component we are building, and imports it as well. So far, so good.
However when I double-quote the options (PKG_CONFIGURE_OPTS) for the configure script:
"${CLFS_SOURCES}/${PKG_NAME}-${PKG_VERSION}/configure" "${PKG_CONFIGURE_OPTS}"`
I get the following error:
gcc: error: unrecognized command line option ‘--with-sysroot=/root/LiLi/target/cross-tools/arm-linux-musleabihf’
If I leave it not quoted like:
"${CLFS_SOURCES}/${PKG_NAME}-${PKG_VERSION}/configure" ${PKG_CONFIGURE_OPTS}`
it works fine (--with-sysroot= is indeed a valid configure flag for binutils).
Why is this? What can I change so that I can double-quote that portion (going by the bash wisdom that one should double-quote just about everything).

Quoting the variable means the entire thing is passed as a single argument, spaces and newlines included. You want word splitting to be performed so that the string is treated as multiple arguments. That's why leaving it unquoted works.
If you're looking for the "right" way to handle this, I recommend using an array. An array can hold multiple values while also preserving whitespace properly.
PKG_CONFIGURE_OPTS=(--prefix="$TOOLS"
--target="$TARGET"
--with-sysroot="$TOOLS/$TARGET"
--disable-nls
--disable-multilib)
...
"$CLFS_SOURCES/$PKG_NAME-$PKG_VERSION/configure" "${PKG_CONFIGURE_OPTS[#]}"

Related

strip quotes from variable from .env file included in makefile

I have a situation where I have an environment variable with space character inside. Some tools do not like quoting the value of the variable, as they will treat the quote as part of the variable.
This is set in a .env file.
PIP_EXTRA_INDEX_URL="https://token#repo https://token#repo"
When I include and export this .env file in a Makefile, I get this warning.
WARNING: Location '"https://token#repo' is ignored:
it is either a non-existing path or lacks a specific scheme.
But I have seen this behavior as initially mentioned also with other tools. Is there a way to handle this?
In the Makefile, I include it like below.
include .env
export
build:
docker build --build-arg PIP_EXTRA_INDEX_URL -t myimage .
Makefiles are not shell scripts and it is not possible to use the same syntax to define variables in both the shell and in make, except in very limited situations.
In the shell, you can have multiple assignments on the same line or even run programs on the same line. So, if your assignment has whitespace in it you have to quote it as you've done here.
In make, the syntax of an assignment is that all text after the assignment (and leading whitespace) becomes the value of the variable and there is no quoting needed; any quotes that are seen are kept as part of the variable value.
So, in the shell this assignment:
PIP_EXTRA_INDEX_URL='https://token#repo https://token#repo'
sets the shell variable PIP_EXTRA_INDEX_URL to the value https://token#repo https://token#repo ... note the quotes are stripped from the value by the shell.
In make this assignment:
PIP_EXTRA_INDEX_URL='https://token#repo https://token#repo'
sets the shell variable PIP_EXTRA_INDEX_URL to the value 'https://token#repo https://token#repo' ... note the quotes are not stripped from the value by make.
So if you use this value in a recipe like this:
do something "$(PIP_EXTRA_INDEX_URL)"
then make will expand that variable and you'll get:
do something "'https://token#repo https://token#repo'"
(including quotes) and that's your problem.
It works like this.
build:
docker build --build-arg PIP_EXTRA_INDEX_URL=$(PIP_EXTRA_INDEX_URL) -t myimage .

are there security issues with using eval on an environment variable in a bash script?

I have a Bash script in which I call rsync in order to perform a backup to a remote server. To specify that my Downloads folder be backed up, I'm passing "'${HOME}/Downloads'" as an argument to rsync which produces the output:
rsync -avu '/Volumes/Norman Data/Downloads' me#example.com:backup/
Running the command with the variable expanded as above (through the terminal or in the script) works fine, but because of the space in the expanded variable and the fact that the quotes (single ticks) are ignored when included in the variable being passed as part of an argument (see here), the only way I can get it not to choke on the space is to do:
stmt="rsync -avu '${HOME}/Downloads' me#examle.com:backup/"
eval ${stmt}
It seems like there would be some vulnerabilities presented by running eval on anything not 100% private to that script. Am I correct in thinking I should be doing it a different way? If so, any hints for a bash-script-beginner would be greatly appreciated.
** EDIT ** - I actually have a bit more involved use case than. the example above. For the paths passed, I have an array of them, each containing spaces, that I'm then combining into 1 string kind of like
include_paths=(
"'${HOME}/dir_a'"
"'${HOME}/dir_b' --exclude=video"
)
for item in "${include_paths[#]}"
do
inc_args="${inc_args}" ${item}
done
inc_args evaluates to '/Volumes/Norman Data/me/dir_a' '/Volumes/Norman Data/me/dir_b' --exclude=video
which I then try to pass as an argument to rsync but the single ticks are read as literals and it breaks after the 1st /Volumes/Norman because of the space.
rsync -avu "${inc_args}" me#example.com:backup/
Using eval seems to read the single ticks as quotes and executes:
rsync -avu '/Volumes/Norman Data/me/dir_a' '/Volumes/Norman Data/me/dir_b' --exclude=video me#example.com:backup/
like I need it to. I can't seem to get any other way to work.
** EDIT 2 - SOLUTION **
So the 1st thing I needed to do was modify the include_paths array to:
remove single ticks from within double quoted items
move any path-specific flags (ex. --exclude) to their own items directly after the path it should apply to
I then built up an array containing the rsync command and its options, added the expanded include_paths and exclude_paths arrays and the connection string to the remote host.
And finally expanded that array, which ran my entire, properly quoted rsync command. In the end the modified array include_paths is:
include_paths=(
"${HOME}/dir_a"
"${HOME}/dir_b"
"--exclude=video"
"${HOME}/dir_c"
)
and I put everything together with:
cmd=(rsync -auvzP)
for item in "${exclude_paths[#]}"
do
cmd+=("--exclude=${item}")
done
for item in "${include_paths[#]}"
do
cmd+=("${item}")
done
cmd+=("me#example.com:backup/")
set -x
"${cmd[#]}"
Use an array for the commands/option instead of a plain variable.
stmt=(rsync -avu "${HOME}/Dowloads" me#example.com:backup/)
Execute it using the builtin command
command "${stmt[#]}"
...Or I personally just put the options/arguments in an array.
options=(-avu "${HOME}/Download" me#example.com:backup/)
The execute it using rsync
rsync "${options[#]}"
If you have newer version of bash which that supports the additional P.E. parameter expansion, then you could probably quote the array.
options=(-avu "${HOME}/Download" me#example.com:backup/)
Check the output by applying the P.E.
echo "${options[#]#Q}"
Should print
'-avu' '/Volumes/Norman Data/Downloads' 'me#examle.com:backup/'
Then you can just
rsync "${options[#]#Q}"

bash: Unable to run build commands via bash scripts

I am trying to run 'make' command from my bash script to build the code.
I can see that all parameters got assigned and able to display the command that i am trying to run. I could not see any issue with the command. But the issue is when it tries to run the command via bash script it fails.
My command is :- ./build_script LIC=1 DOCUMNETS=1 PROJECTS="cuda bfm"
Script Snippet of parsing all the arguments and constructing make command:-
makeargs=""
for build_flag do
if [[ "$build_flag " = "PROJECTS="* ]]; then
apps =`echo $build_flag |sed "s/PROJECTS=//" `
makeargs="$makeargs PROJECTS=\"$apps \""
else
makeargs="$makeargs $build_flag"
fi
done
echo "make DCOV=1 $makeargs $maketest"
make DCOV=1 $makeargs $maketest
When i run the script, I can see the build command has constructed properly.
Output :-
make DCOV=1 LIC=1 DOCUMNETS=1 PROJECTS="cuda bfm" run_all
GNUmakefile:16: warning: overriding commands for target `/'
GNUmakefile:19: warning: ignoring old commands for target `/'
make: *** No rule to make target `bfm"'. Stop.
I try to print PROJECTS variable in my 'GNUmakefile' present in build_main folder. I can see the output : PROJECTS is "bfm . It is not taking whole "cuda bfm" as a whole string.
Note:- When i try to run the same build command :- make DCOV=1 LIC=1 DOCUMNETS=1 PROJECTS="cuda bfm" run_all explicitly it works fine.
Seems like issue with Interpreting variables with makefile.
Any solution for this ? Please help.
Thanks!
Change makeargs string to array before passing it as an arguments group.
eval makeargs_array=( $makeargs )
make UVC=1 "${makeargs_array[#]}" $maketest
Without converting to array, if you enable debug, it shows last line interpretation as
make DCOV=1 LIC=1 DOCUMNETS=1 'PROJECTS="cuda' bfm '"'
Which is clearly ignoring double-quote and considering space as separator.
Even double-quote is getting passed as a separate argument in this case.
Explanation:
Word-splitting
It says,
The shell scans the results of parameter expansion, command
substitution, and arithmetic expansion that did not occur within
double quotes for word splitting.
If we use "$makeargs" i.e. surrounded by double-quote, it is not considered by word-splitting and results in LIC=1 DOCUMNETS=1 "PROJECTS=cuda bfm"
But again its a complete string, while requirement is to split the string to use as arguments.
So now using $makeargs.
Word-splitting gets in action as per the default IFS (space, tab, newline), we get result as LIC=1 DOCUMNETS=1 PROJECTS="cuda bfm "
Double-quoted part of string didn't affect the word-splitting since, subject to splitting is complete string here.
Why array worked here?
Array itself expands each element as separate word when using # and here no further word-splitting requires after expansion.
Arrays

How to pass make flags stored in text files in command line?

I have a text file called OPTIONS.txt storing all flags of Makefile:
arg1=foo arg2="-foo -bar"
I want to pass all flags in this file to make. However,
make `cat OPTIONS.txt`
fails with make: invalid option -- 'a'. It seems that shell interprets it as:
make arg1=foo arg2="-foo -bar"
^argv[1] ^argv[2] ^argv[3]
Is there any way to make it interpreted as:
make arg1=foo arg2="-foo -bar"
^argv[1] ^--------argv[2]
Since you control the options file, store the options one per line:
arg1=foo
arg2="-foo -bar"
Then in the shell, you'll read the file into an array, one element per line:
readarray -t opts < OPTIONS.txt
Now you can invoke make and keep the options whole:
make "${opts[#]}"
If you want the shell to interpret quotes after backtick expansion you need to use eval, like this:
eval make `cat OPTIONS.txt`
however just be aware that this evaluates everything, so if you have quoted content outside of the backticks you'll get the same issue:
eval make `cat OPTIONS.txt` arg4="one two"
will give an error. You'd have to double-quote the arg4, something like this:
eval make `cat OPTIONS.txt` arg4='"one two"'
In general it's tricky to do stuff like this from the command line, outside of scripts.
ETA
The real problem here is that we don't have a set of requirements. Why do you want to put these into a file, and what kind of things are you adding; are they only makefile variable assignments, or are there other make options here as well such as -k or similar?
IF the OP controls (can change) the format of the file AND the file contains content only used by make AND the OP doesn't care about the variables being command line assignments vs. regular assignments AND there are only variable assignments and not other options, then they can just (a) put each variable assignment on its own line, (b) remove all quotes, and (c) use include OPTIONS.txt from inside the makefile to "import" them.

How do I locally source environment variables that I have defined in a Docker-format env-file?

I've written a bunch of environment variables in Docker format, but now I want to use them outside of that context. How can I source them with one line of bash?
Details
Docker run and compose have a convenient facility for importing a set of environment variables from a file. That file has a very literal format.
The value is used as is and not modified at all. For example if the value is surrounded by quotes (as is often the case of shell variables), the quotes are included in the value passed
Lines beginning with # are treated as comments and are ignored
Blank lines are also ignored.
"If no = is provided and that variable is…exported in your local environment," docker "passes it to the container"
Thankfully, whitespace before the = will cause the run to fail
so, for example, this env-file:
# This is a comment, with an = sign, just to mess with us
VAR1=value1
VAR2=value2
USER
VAR3=is going to = trouble
VAR4=this $sign will mess with things
VAR5=var # with what looks like a comment
#VAR7 =would fail
VAR8= but what about this?
VAR9="and this?"
results in these env variables in the container:
user=ubuntu
VAR1=value1
VAR2=value2
VAR3=is going to = trouble
VAR4=this $sign will mess with things
VAR5=var # with what looks like a comment
VAR8= but what about this?
VAR9="and this?"
The bright side is that once I know what I'm working with, it's pretty easy to predict the effect. What I see is what I get. But I don't think bash would be able to interpret this in the same way without a lot of changes. How can I put this square Docker peg into a round Bash hole?
tl;dr:
source <(sed -E -e "s/^([^#])/export \1/" -e "s/=/='/" -e "s/(=.*)$/\1'/" env.list)
You're probably going to want to source a file, whose contents
are executed as if they were printed at the command line.
But what file? The raw docker env-file is inappropriate, because it won't export the assigned variables such that they can be used by child processes, and any of the input lines with spaces, quotes, and other special characters will have undesirable results.
Since you don't want to hand edit the file, you can use a stream editor to transform the lines to something more bash-friendly. I started out trying to solve this with one or two complex Perl 5 regular expressions, or some combination of tools, but I eventually settled on one sed command with one simple and two extended regular expressions:
sed -E -e "s/^([^#])/export \1/" -e "s/=/='/" -e "s/(=.*)$/\1'/" env.list
This does a lot.
The first expression prepends export to any line whose first character is anything but #.
As discussed, this makes the variables available to anything else you run in this session, your whole point of being here.
The second expression simply inserts a single-quote after the first = in a line, if applicable.
This will always enclose the whole value, whereas a greedy match could lop off some of (e.g.) VAR3, for example
The third expression appends a second quote to any line that has at least one =.
it's important here to match on the = again so we don't create an unmatched quotation mark
Results:
# This is a comment, with an =' sign, just to mess with us'
export VAR1='value1'
export VAR2='value2'
export USER
export VAR3='is going to = trouble'
export VAR4='this $sign will mess with things'
export VAR5='var # with what looks like a comment'
#VAR7 ='would fail'
export VAR8=' but what about this?'
export VAR9='"and this?"'
Some more details:
By wrapping the values in single-quotes, you've
prevented bash from assuming that the words after the space are a command
appropriately brought the # and all succeeding characters into the VAR5
prevented the evaluation of $sign, which, if wrapped in double-quotes, bash would have interpreted as a variable
Finally, we'll take advantage of process substitution to pass this stream as a file to source, bring all of this down to one line of bash.
source <(sed -E -e "s/^([^#])/export \1/" -e "s/=/='/" -e "s/(=.*)$/\1'/" env.list)
Et voilà!

Resources