I'm working on a Makefile which takes a command line parameter, runs a translation (tr) on it, then uses that variable to create a directory and also prints the result. When the variable is printed, it is as I expect (the translated name), however when the variable is used to create the directory, the results are incorrect.
So for example, when I run:
make build=debug
I see:
Building...
***************************
BuilD Type: Debug
Build Target: x86
But the resultant file system is:
out/
└─── debug | tr d D_x86
I thought that running the translation on the input variable build and assigning it to $BUILD_TYPE would have set this new variable to "Debug" but it seems like that is not the case all the time... How does this actually work?
Full Makefile:
BUILD_TYPE=Release
BUILD_TARGET=Arm
OUTPUT_DIR=out
MKDIR_P = mkdir -p
ifeq ($(build),debug)
BUILD_TYPE='$(build) | tr d D'
endif
ifeq ($(target),x86)
BUILD_TARGET=$(target)
endif
TT=$(BUILD_TYPE)_$(BUILD_TARGET)
all: directories
#echo 'Building...'
#echo '***************************'
#echo 'Build Type: $(BUILD_TYPE)'
#echo 'Build Target: $(BUILD_TARGET)'
#echo 'Output Directory: $(PWD)/$(OUTPUT_DIR)'
#echo '***************************'
directories:
${MKDIR_P} $(OUTPUT_DIR)
${MKDIR_P} $(OUTPUT_DIR)/$(TT)
This line:
BUILD_TYPE='$(build) | tr d D'
is not running a shell command. That is a literal string assignment.
You are assigning to the variable BUILD_TYPE the literal string 'debug | tr d D'.
To do what you want you need to actually run a shell command. Something like this for example.
BUILD_TYPE:=$(shell echo '$(build)' | tr d D)\
As to why you are seeing things "correctly" on the output line that's because you happen to be generating a valid shell pipeline there and make is running it.
That is this line
#echo 'Build Type: $(BUILD_TYPE)'
becomes
#echo 'Build Type: 'debug | tr d D''
which, you'll notice, is a valid shell line. Also notice that you get BuilD type and not Build type printed out?
Related
I have two functions in Bash. One is a generic run function, that accepts an input and evaluates it, while printing the command, and testing the exit code. This is used in a large script to ensure each command executes successfully before continuing.
The second one is a complex function, that is doing some Git history parsing. The problematic line is the only one shown.
I am calling this function from a for-loop, that iterates over a list of terms to search. The issue is that spaces are not being handled correctly, when between other words. I have tried running my script though shell-checking websites, and all of the suggestions seem to break my code.
function run() {
echo "> ${1}"
eval "${1}"
# Test exit code of the eval, and exit if non-zero
}
function searchCommitContents() {
run 'result=$(git log -S'"${1}"' --format=format:%H)'
# Do something with result, which is a list of matching SHA1 hashes for the commits
echo "${result}"
}
# Main
declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')
for i in "${searchContents[#]}"
do
searchCommitContents "${i}"
done
Here is the output I get:
> result=$(git log -Sfoo --format=format:%H)
<results>
> result=$(git log -Sbar --format=format:%H)
<results>
> result=$(git log -S foo --format=format:%H)
<results>
> result=$(git log -Sfoo bar --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
I tried to add additional single and double-quotes to various areas of the code, such that the 'foo bar' string would not resolve to two different words. I also tried adding an escape to the dollar sign, like so: -s'"\${1}"' based on other questions on this site.
Why are you printing result=$(? It's an internal variable, it can be anything, there is no need for it in logs.
Print the command that you are executing, not the variable name.
run() {
echo "+ $*" >&2
"$#"
}
searchCommitContents() {
local result
result=$(run git log -s"${1}" --format=format:%H)
: do stuff to "${result}"
echo "$result"
}
issue with an input that has a space in the middle.
If you want quoted string, use printf "%q" or ${...#Q} for newer Bash, but I don't really enjoy both quoting methods and just use $*. I really like /bin/printf from GNU coreutils, but it's a separate process... while ${..#Q} is the fastest, it's (still) not enough portable for me (I have some old Bash around).
# compare
$ set -- a 'b c' d
$ echo "+ $*" >&2
+ a b c d
$ echo "+$(printf " %q" "$#")" >&2
+ a b\ \ c d
$ echo "+" "${##Q}" >&2
+ 'a' 'b c' 'd'
$ echo "+$(/bin/printf " %q" "$#")" >&2
+ a 'b c' d
See these lines:
> result=$(git log -Sfoo bar --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.
Specifically this: -Sfoo bar. It should be -S"foo bar" or -S "foo bar". Because to pass an argument with spaces, we need to quote the argument. But, each time the argument pass through a command/function layer, one layer of quote ('', "") is extracted. So, we need to nest the quote.
So in this line:
declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')
change 'foo bar' to '"foo bar"' or "'foo bar'" or "\"foo bar\"".
This is a case of 2 layers nested quotes. The more the layer, the trickier it gets. Here's an example of 4 layers quotes I once did.
I have 2 files .c which only contain a printf("x")
I am in bash script and i want to check if the values in the printf are for project1.c =20 and for project 2 =10,and then make some changes depending on the values.
How am i supposed to make the comparison in the if command?
This is what i have tried to do,not sure if it is right way.
for d in $1/*/*
do
gcc project1 project1.c
if[ ./project1 = 20 ];then
$project1 =30
else
$project1 =0
fi
gcc project2 project2.c
if[ ./project2 =10 ];then
$project2 = 70
else
$project2 = 0
fi
sum=$project1 + $project2
echo "project1 : $project1 project2: $project2 total grade: $sum" >> grades.txt
done
fi
Your invocation of gcc is wrong. You have to specify the output file:
gcc -o project1 project1.c
Next, in shell, variable substitution is a different process than assignment. So, you can't write $var=foo. The correct syntax is var=foo.
Then, space is a special character (it is used to separate arguments). So var=foo is not the same than var = foo. So, the correct syntax is:
project1=30
Next, in shell, the pattern $(command) is replaced by the result of command. So. I have to do:
if [ $(./project2) == 10 ]; then
Finally, you can do arithmetic using $((calculus)). So, you have to write:
sum=$(($project1 + $project2))
I have a shell script that consist of two files, one bash-file (main.sh) and one file holding all my config-variables(vars.config).
vars.config
domains=("something.com" "else.something.com")
something_com_key="key-to-something"
else_something_com_key="key-to-something else"
In my code i want to loop through the domains array and get the key for the domain.
#!/usr/bin/env sh
source ./vars.config
key="_key"
for i in ${domains[#]};
do
base="$(echo $i | tr . _)" # this swaps out . to _ to match the vars
let farmid=$base$key
echo $farmid
done
So when i run it i get an error message
./main.sh: line 13: let: key-to-something: syntax error: operand
expected (error token is "key-to-something")
So it actually swaps it out, but i cant save it to a variable.
You can expand a variable to the value of its value using ${!var_name}, for example in your code you can do:
key="_key"
for i in ${domains[#]};
do
base="$(echo $i | tr . _)" # this swaps out . to _ to match the vars
farmid=$base$key
farmvalue=${!farmid}
echo $farmvalue
done
I'm looking for a standard tool capable of taking all of its arguments and turning it into a single string suitable for use as multiple arguments in an automatically generated bash/sh/zsh script. Such a command is extremely useful in various disciplines of script-fu. An example of its usage:
% shsafe 'A big \nasty string '\'' $HOME $PATH' 'another string \\'
'A big \nasty string '\'' $HOME $PATH' 'another string \\'
Using it in another script:
% sshc host rm 'file/with spaces and $special chars'
where sshc contains
#!/bin/bash
# usage: sshc host command [arg ...]
# Escapes its arguments so that the command may contain special
# characters. Assumes the remote shell is sh-like.
host=$1
shift
exec ssh "$host" "$(shsafe "$#")"
Another example:
#!/bin/bash
# Run multiple commands in a single sudo session. The arguments of
# this script are passed as arguments to the first command. Useful if
# you don't want to have to type the password for both commands and
# the first one takes a while to run.
sudo bash -c "pacman -Syu $(shsafe "$#") && find /etc -name '*.pacnew'"
I couldn't find a suitable solution to this problem in the pre-existing commands, so I made up my own, called shsafe. It uses the fact that single quotes, '', turn off absolutely all shell expansion, except for ' itself.
shsafe:
#!/usr/bin/env python
from sys import *
n = len(argv)
if n == 1:
exit(0)
i = 1
while True:
stdout.write("'" + argv[i].replace("'", "'\\''") + "'")
i += 1
if i == n:
break
stdout.write(' ')
stdout.write('\n')
Is there any standard tool capable of doing this to its arguments?
Note that the printf command with a format string consisting of just the %q formatter is not good enough for this, because it won't keep multiple arguments separated:
% printf %q arg1 arg2
arg1arg2
I did eventually figure out a decent way of doing this:
% printf "$'%q' " 'crazy string \ $HOME' 'another\ string'
$'crazy\ string\ \\\ \$HOME' $'another\\\ string'
It's a little error prone what with the quotes everywhere, so it's not ideal, IMO, but it's a solid solution that should work anywhere. If it's being used a lot, you could always turn it into a shell function:
shsafe () {
printf "$'%q' " "$#"
}
I wrote simple script as follow
#!/bin/bash
auth_type=""
SM_Read-only="Yes"
SM_write-only="No"
echo -e ${SM_Read-only}
echo -e ${SM_Write-only}
if [ "${SM_Read-only}" == "Yes" ] && [ "${SM_Write-only}" == "Yes" ]
then
auth_type="Read Write"
else
auth_type="Read"
fi
echo -e $auth_type
And when i execute it i got following output with errors.
./script.bash: line 5: SM_Read-only=Yes: command not found
./script.bash: line 6: SM_write-only=No: command not found
only
only
Read
Any one know correct way to declare the variable with "-" (dash)?
EDIT:
have getting response from c code and evaluate the variables for example
RESP=`getValue SM_ Read-only ,Write-only 2>${ERR_DEV}`
RC=$?
eval "$RESP"
from above scripts code my c binary getValue know that script want Read-only and Write-only and return value to script.So during eval $RESP in cause error and in my script i access variable by
echo -e ${SM_Read-only}
echo -e ${SM_Write-only}
which also cause error.
Rename the variable name as follows:
SM_Read_only="Yes"
SM_write_only="No"
Please, don't use - minus sign in variable names in bash, please refer to the answer, on how to set the proper variable name in bash.
However if you generate the code, based on others output, you can simply process their output with sed:
RESP=$(getValue SM_ Read-rule,Write-rule 2>${ERR_DEV}|sed "s/-/_/g")
RC=$?
eval "$RESP"
- is not allowed in shell variable names. Only letters, numbers, and underscore, and the first character must be a letter or underscore.
I think you cant have a dash in your variables names, only letters, digits and "_"
Try:
SM_Read_only
Or
SM_ReadOnly