How to escape special chars in variable when using makefile? - makefile

supposed I have a makefile with show method
show::
echo $(VAR)
It would output foobar when execute make VAR=foobar show as expected.
However, when VAR is some hashstring such as $2y$10$Gae9mVS, everything goes wrong.
make VAR=$2y$10$Gae9mVS show shows y0, but not $2y$10$Gae9mVS
Could someone give me some hint? Thanks.

You have two (instances) of the issue here. Neither of them has anything to do with make.
Try echo VAR=$2y$10$Gae9mVS and see what you get.
Now try echo 'VAR=$2y$10$Gae9mVS'.
Then try make 'VAR=$2y$10$Gae9mVS' show.
Then add single quotes to your echo '$(VAR)' line and try make 'VAR=$2y$10$Gae9mVS' show again.
In short:
Quote your variables.
As pointed out in the comments there is one additional issue here and this one is actually related to make.
When you ask make to expand $(VAR) it recursively expands the variables and sees the $ in the value as variables to expand.
You can use echo '$(value VAR)' to avoid this or use make 'VAR=$$2y$$10$$Gae9mVS' show to escape the $ from make itself.
Unfortunately, I'm not sure you can do this in a transparent fashion. That is it requires you to know either how the variable will be used (though any used in make itself will have this expansion problem) or know that your variable needs to not be recursively expanded at the usage site.

It will do exactly what you will get with
$ VAR=$2y$10$Gae9mVS && echo $VAR
y0
This happens at shell level; single-quote the string.
$ VAR='$2y$10$Gae9mVS' && echo $VAR
$2y$10$Gae9mVS

Related

Equal/minus sign without a colon in a parameter expasion in bash

I found a snippet like this in a Bash script recently:
$ echo ${A=3}
Now, I know that ${A:=3} would set the variable A if A is "falsy", or ${A:-3} would return 3 if A is "falsy." I have never seen these similar expressions without the colon though, and I cannot find the explanation for these colon-less expressions in the Bash's documentation.
What is going on here?
Actually, the documentation does explain what is going on here, even if burying the lede a bit:
When not performing substring expansion, using the form described below (e.g., ‘:-’), Bash tests for a parameter that is unset or null. Omitting the colon results in a test only for a parameter that is unset. Put another way, if the colon is included, the operator tests for both parameters’ existence and that its value is not null; if the colon is omitted, the operator tests only for existence.
In practice, this means that they behave the same way if the variables are unset:
$ echo ${A=no-colon}
no-colon
$ echo ${B:=with-colon}
with-colon
$ echo $A
no-colon
$ echo $B
with-colon
However, if the variables are set to the empty string, then the behavior is different. The expression with a colon will set the variable and return the value, and the one without will leave the variable as is (i.e., set to the empty string) and return its empty value:
$ A='' ; B=''
$ echo ${A=no-colon}
$ echo ${B:=with-colon}
with-colon
$ echo $A
$ echo $B
with-colon
As stated in the documentation, the same behavior applies to the other "operators" (-, ?, +).
Posting it in the spirit of Can I answer my own question? and because it took a surprisingly long time for me to learn it, even after finding it in code. Maybe making it a bit more explicit, with some examples, can help somebody else out there :)

Bad substitution on bash substringing operation

I am trying to get this simple function working:
p4edit(){
p4 edit ${$1:25}
}
I read the other popular bad substitution question on SO and it did not seem to help me or be related to my problem. What am I doing wrong here? I want to cut off the first 25 characters of the argument provided to my function.
I have noticed a simple echo ${"test":3} fails the same way, but this succeeds:
test="test"
echo ${test:3}
I am just running this in a bash instance.
You have too much money! (too many dollar signs). Use:
p4edit(){
p4 edit ${1:25}
}
To extract the 25th-and-onwards characters from $1.
Why two times a $ ?
p4edit(){
echo ${1:25}
}
works fine for me. String functions in bash are a bit tricky, since they are not really consistent. But ${} already defines, that you are looking for a variable. So only submit the name to it. There are some stringfunctions with ${#var} but as far as I know, there is never a $ inside a ${}

Use 'subst' in a multiline makefile bash script?

I read this question: Makefile: $subst in dependency list, but I still can't make my shell script work correctly.
I have a makefile with a line with the contents:
##public_detailed#|test_create|Syntax: commoncmdsyntax test_create test_name=<test-name>
A target runs a multiline bash script, where the commoncmdsyntax must be replaced by a string containing words and spaces.
In the script, I use cut to assign to a variable desc the following string:
Syntax: commoncmdsyntax test_create test_name=<test-name>
The problem is that commoncmdsyntax is not replaced by new text here:
$(subst commoncmdsyntax,new text,$$desc)
I also tried to replace it by a single word, like XX, but it also does not work.
The subst function (as in $(subst commoncmdsyntax,new text,$$desc)) is a Make function, so Make will perform the substitution before running any rule and therefore before your script assigns a value to desc. So even if secondary expansion worked the way you seem to think it will, this approach would still fail.
If you want to perform a substitution within something made by a shell script (in a recipe), the sensible way is to do so within the recipe:
echo $dest | sed 's/commoncmdsyntax/new text/'
We can give you a more detailed solution if you give us a minimal complete example of the problem.

variable substitution removing quotes

I seem to have some difficulty getting what I want to work. Basically, I have a series of variables that are assigned strings with some quotes and \ characters. I want to remove the quotes to embed them inside a json doc, since json hates quotes using python dump methods.
I figured it would be easy. Just determine how to remove the characters easy and then write a simple for loop for the variable substitution, well it didn't work that way.
Here is what I want to do.
There is a variable called "MESSAGE23", it contains the following "com.centrify.tokend.cac", I want to strip out the quotes, which to me is easy, a simple echo $opt | sed "s/\"//g". When I do this from the command line:
$> MESSAGE23="com."apple".cacng.tokend is present"
$> MESSAGE23=`echo $MESSAGE23 | sed "s/\"//g"`
$> com.apple.cacng.tokend is present
This works. I get the properly formatted string.
When I then try to throw this into a loop, all hell breaks loose.
for i to {1..25}; do
MESSAGE$i=`echo $MESSAGE$i | sed "s/\"//g"`
done
This doesn't work (either it throws a bunch of indexes out or nothing), and I'm pretty sure I just don't know enough about arg or eval or other bash substitution variables.
But basically I want to do this for another set of variables with the same problems, where I strip out the quotes and incidentally the "\" too.
Any help would be greatly appreciated.
You can't do that. You could make it work using eval, but that introduces another level of quoting you have to worry about. Is there some reason you can't use an array?
MESSAGE=("this is MESSAGE[0]" "this is MESSAGE[1]")
MESSAGE[2]="I can add more, too!"
for (( i=0; i<${#MESSAGE[#]}; ++i )); do
echo "${MESSAGE[i]}"
done
Otherwise you need something like this:
eval 'echo "$MESSAGE'"$i"'"'
and it just gets worse from there.
First, a couple of preliminary problems: MESSAGE23="com."apple".cacng.tokend is present" will not embed double-quotes in the variable value, use MESSAGE23="com.\"apple\".cacng.tokend is present" or MESSAGE23='com."apple".cacng.tokend is present' instead. Second, you should almost always put double-quotes around variable expansions (e.g. echo "$MESSAGE23") to prevent parsing oddities.
Now, the real problems: the shell doesn't allow variable substitution on the left side of an assignment (i.e. MESSAGE$i=something won't work). Fortunately, it does allow this in a declare statement, so you can use that instead. Also, when the sees $MESSAGE$i it replaces it will the value of $MESSAGE followed by the value of $i; for this you need to use indirect expansion (`${!metavariable}').
for i in {1..25}; do
varname="MESSAGE$i"
declare $varname="$(echo "${!varname}" | tr -d '"')"
done
(Note that I also used tr instead of sed, but that's just my personal preference.)
(Also, note that #Mark Reed's suggestion of an array is really the better way to do this sort of thing.)

Tricky brace expansion in shell

When using a POSIX shell, the following
touch {quick,man,strong}ly
expands to
touch quickly manly strongly
Which will touch the files quickly, manly, and strongly, but is it possible to dynamically create the expansion? For example, the following illustrates what I want to do, but does not work because of the order of expansion:
TEST=quick,man,strong #possibly output from a program
echo {$TEST}ly
Is there any way to achieve this? I do not mind constricting myself to Bash if need be. I would also like to avoid loops. The expansion should be given as complete arguments to any arbitrary program (i.e. the program cannot be called once for each file, it can only be called once for all files). I know about xargs but I'm hoping it can all be done from the shell somehow.
... There is so much wrong with using eval. What you're asking is only possible with eval, BUT what you might want is easily possible without having to resort to bash bug-central.
Use arrays! Whenever you need to keep multiple items in one datatype, you need (or, should use) an array.
TEST=(quick man strong)
touch "${TEST[#]/%/ly}"
That does exactly what you want without the thousand bugs and security issues introduced and concealed in the other suggestions here.
The way it works is:
"${foo[#]}": Expands the array named foo by expanding each of its elements, properly quoted. Don't forget the quotes!
${foo/a/b}: This is a type of parameter expansion that replaces the first a in foo's expansion by a b. In this type of expansion you can use % to signify the end of the expanded value, sort of like $ in regular expressions.
Put all that together and "${foo[#]/%/ly}" will expand each element of foo, properly quote it as a separate argument, and replace each element's end by ly.
In bash, you can do this:
#!/bin/bash
TEST=quick,man,strong
eval echo $(echo {$TEST}ly)
#eval touch $(echo {$TEST}ly)
That last line is commented out but will touch the specified files.
Zsh can easily do that:
TEST=quick,man,strong
print ${(s:,:)^TEST}ly
Variable content is splitted at commas, then each element is distributed to the string around the braces:
quickly manly strongly
Taking inspiration from the answers above:
$ TEST=quick,man,strong
$ touch $(eval echo {$TEST}ly)

Resources