Variable expansion in Makefile - makefile

It's part of code in a makefile, there are two $$ to expand variable CONTINUE,why? Are there any special meaning?
read -r -p "Overwrite your existing default nginx configuration? [y/N] " CONTINUE;
if [ "$$CONTINUE" == "y" ] || [ "$$CONTINUE" == "Y" ]; then
echo "y"
else
echo "n"
fi

$$ is (simply put) escaping in a Makefile because $-Vars also exists there.
To use the variable for bash (which also needs $var) you have to write $$
pre edit (topic changes from "script" to "makefile")
Does this script even work?
# ./test.sh
Overwrite your existing default nginx configuration? [y/N] y
n
Change it to a single $.
$$ expands to the PID of the executing bash shell
($$) Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the invoking shell, not the subshell.
Bash ref

Related

In bash, either exit script without exiting the shell or export/set variables from within subshell

I have a function that runs a set of scripts that set variables, functions, and aliases in the current shell.
reloadVariablesFromScript() {
for script in "${scripts[#]}"; do
. "$script"
done
}
If one of the scripts has an error, I want to exit the script and then exit the function, but not to kill the shell.
reloadVariablesFromScript() {
for script in "${scripts[#]}"; do
{(
set -e
. "$script"
)}
if [[ $? -ne 0 ]]; then
>&2 echo $script failed. Skipping remaining scripts.
return 1
fi
done
}
This would do what I want except it doesn't set the variables in the script whether the script succeeds or fails.
Without the subshell, set -e causes the whole shell to exit, which is undesirable.
Is there a way I can either prevent the called script from continuing on an error without killing the shell or else set/export variables, aliases, and functions from within a subshell?
The following script simulates my problem:
test() {
{(
set -e
export foo=bar
false
echo Should not have gotten here!
export bar=baz
)}
local errorCode=$?
echo foo="'$foo'". It should equal 'bar'.
echo bar="'$bar'". It should not be set.
if [[ $errorCode -ne 0 ]]; then
echo Script failed correctly. Exiting function.
return 1
fi
echo Should not have gotten here!
}
test
If worst comes to worse, since these scripts don't actually edit the filesystem, I can run each script in a subshell, check the exit code, and if it succeeds, run it outside of a subshell.
Note that set -e has a number of surprising behaviors -- relying on it is not universally considered a good idea. That caveat being give, though: We can shuffle environment variables, aliases, and shell functions out as text:
envTest() {
local errorCode newVars
newVars=$(
set -e
{
export foo=bar
false
echo Should not have gotten here!
export bar=baz
} >&2
# print generate code which, when eval'd, recreates our functions and variables
declare -p | egrep -v '^declare -[^[:space:]]*r'
declare -f
alias -p
); errorCode=$?
if (( errorCode == 0 )); then
eval "$newVars"
fi
printf 'foo=%q. It should equal %q\n' "$foo" "bar"
printf 'bar=%q. It should not be set.\n' "$bar"
if [[ $errorCode -ne 0 ]]; then
echo 'Script failed correctly. Exiting function.'
return 1
fi
echo 'Should not have gotten here!'
}
envTest
Note that this code only evaluates either export should the entire script segment succeed; the question text and comments appear to indicate that this is acceptable if not desired.

How to execute a file that is located in $PATH

I am trying to execute a hallo_word.sh that is stored at ~/bin from this script that is stored at my ~/Desktop. I have made both scripts executable. But all the time I get the problem message. Any ideas?
#!/bin/sh
clear
dir="$PATH"
read -p "which file you want to execute" fl
echo ""
for fl in $dir
do
if [ -x "$fl" ]
then
echo "executing=====>"
./$fl
else
echo "Problem"
fi
done
This line has two problems:
for fl in $dir
$PATH is colon separated, but for expects whitespace separated values. You can change that by setting the IFS variable. This changes the FIELD SEPARATOR used by tools like for and awk.
$fl contains the name of the file you want to execute, but you overwrite its value with the contents of $dir.
Fixed:
#!/bin/sh
clear
read -p "which file you want to execute" file
echo
IFS=:
for dir in $PATH ; do
if [ -x "$dir/$file" ]
then
echo "executing $dir/$file"
exec "$dir/$file"
fi
done
echo "Problem"
You could also be lazy and let a subshell handle it.
PATH=(whatever) bash command -v my_command
if [ $? -ne 0 ]; then
# Problem, could not be found.
else
# No problem
fi
There is no need to over-complicate things.
command(1) is a builtin command that allows you to check if a command exists.
The PATH value contains all the directories in which executable files can be run without explicit qualification. So you can just call the command directly.
#!/bin/sh
clear
# r for raw input, e to use readline, add a space for clarity
read -rep "Which file you want to execute? " fl || exit 1
echo ""
"$fl" || { echo "Problem" ; exit 1 ; }
I quote the name as it could have spaces.
To test if the command exists before execution use type -p
#!/bin/sh
clear
# r for raw input, e to use readline, add a space for clarity
read -rep "Which file you want to execute? " fl || exit 1
echo ""
type -p "$fq" >/dev/null || exit 1
"$fl" || { echo "Problem" ; exit 1 ; }

Setting a variable within a bash PS1

I'm trying to customize my bash prompt and I'm having trouble with a few conditionals.
My current PS1 looks like this.
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$? = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
The first 6 lines just set regular PS1 stuff.
Line 7 then calls a function to display the current git branch and status if applicable.
Line 8 then tests the return code of the previous command and changes the colour of the $ on the end.
Line 9 sets the prompt back to white ready for the user's command.
However line 8 is responding to the return code from line 7's function and not the previous command as I first expected.
I've tried moving line 8 before line 7 and eveything works as it should. But I don't want line 8 before line 7, the $ must be on the end.
I've tried setting a variable earlier on to be the value of $? and then testing that variable like so
export PS1="\
\`RETURN=\$?\`\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$RETURN = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
But this doesn't work.
Does anybody have any idea how to solve my problem?
The proper way is to use PROMPT_COMMAND like so:
prompt_cmd () {
LAST_STATUS=$?
PS1="$PS1USERCOLOR\u"
PS1+="$COLOR_WHITE#"
PS1+="$COLOR_GREEN\h"
PS1+="$COLOR_WHITE:"
PS1+="$COLOR_YELLOW\W"
if type parse_git_branch > /dev/null 2>&1; then
PS1+=$(parse_git_branch)
fi
if [[ $LAST_STATUS = 0 ]]; then
PS1+="$COLOR_WHITE"
else
PS1+="$COLOR_RED"
fi
PS1+='\$'
PS1+="$COLOR_WHITE"
}
Since PROMPT_COMMAND is evaluated prior to each prompt, you simply execute code that sets PS1 they way you like for each prompt instance, rather than trying to embed deferred logic in the string itself.
A couple of notes:
You must save $? in the first line of the code, before the value you want is overwritten.
I use double quotes for most of the steps, except for \$; you could use PS1+="\\\$" if you like.
The standard solution to this problem is to make use of the bash environment variable PROMPT_COMMAND. If you set this variable to the name of a shell function, said function will be executed before each bash prompt is shown. Then, inside said function, you can set up whatever variables you want. Here's how I do almost exactly what you're looking for in my .bashrc:
titlebar_str='\[\e]0;\u#\h: \w\a\]'
time_str='\[\e[0;36m\]\t'
host_str='\[\e[1;32m\]\h'
cwd_str='\[\e[0;33m\]$MYDIR'
git_str='\[\e[1;37m\]`/usr/bin/git branch --no-color 2> /dev/null | /bin/grep -m 1 ^\* | /bin/sed -e "s/\* \(.*\)/ [\1]/"`\[\e[0m\]'
dolr_str='\[\e[0;`[ $lastStatus -eq 0 ] && echo 32 || echo 31`m\]\$ \[\e[0m\]'
export PS1="$titlebar_str$time_str $host_str $cwd_str$git_str$dolr_str"
function prompt_func {
# Capture the exit status currently in existence so we don't overwrite it with
# any operations performed here.
lastStatus=$?
# ... run some other commands (which will have their own return codes) to set MYDIR
}
export PROMPT_COMMAND=prompt_func
Now bash will run prompt_func before displaying each new prompt. The exit status of the preceding command is captured in lastStatus. Because git_str, dolr_str, etc. are defined with single quotes, the variables (including lastStatus) and commands inside them are then re-evaluated when bash dereferences PS1.
Solved it!
I need to use the PROMPT_COMMAND variable to set the RETURN variable. PROMPT_COMMAND is a command which is called before PS1 is loaded.
My script now looks like
PROMPT_COMMAND='RETURN=$?'
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [[ \$RETURN = 0 ]]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"

How can I differentiate "command substitution" from "subshell" inside a script?

I need to differentiate two cases: ( …subshell… ) vs $( …command substitution… )
I already have the following function which differentiates between being run in either a command substitution or a subshell and being run directly in the script.
#!/bin/bash
set -e
function setMyPid() {
myPid="$(bash -c 'echo $PPID')"
}
function echoScriptRunWay() {
local myPid
setMyPid
if [[ $myPid == $$ ]]; then
echo "function run directly in the script"
else
echo "function run from subshell or substitution"
fi
}
echoScriptRunWay
echo "$(echoScriptRunWay)"
( echoScriptRunWay; )
Example output:
function run directly in the script
function run from subshell or substitution
function run from subshell or substitution
Desired output
But I want to update the code so it differentiates between command substitution and subshell. I want it to produce the output:
function run directly in the script
function run from substitution
function run from subshell
P.S. I need to differentiate these cases because Bash has different behavior for the built-in trap command when run in command substitution and in a subshell.
P.P.S. i care about echoScriptRunWay | cat command also. But it's new question for me which i created here.
I don't think one can reliably test if a command is run inside a command substitution.
You could test if stdout differs from the stdout of the main script, and if it does, boldly infer it might have been redirected. For example
samefd() {
# Test if the passed file descriptors share the same inode
perl -MPOSIX -e "exit 1 unless (fstat($1))[1] == (fstat($2))[1]"
}
exec {mainstdout}>&1
whereami() {
if ((BASHPID == $$))
then
echo "In parent shell."
elif samefd 1 $mainstdout
then
echo "In subshell."
else
echo "In command substitution (I guess so)."
fi
}
whereami
(whereami)
echo $(whereami)

Shell equivalent of Perl's die

Is there a shell equivalent (in either bash or zsh) of Perl's die function?
I want to set the exit code and print a message in a single line. I know I can make my own simple function but I'm kind of hoping for a built in.
Nope, you need both
echo and exit
Just make a shell function like this :
die() {
[[ $1 ]] || {
printf >&2 -- 'Usage:\n\tdie <message> [return code]\n'
[[ $- == *i* ]] && return 1 || exit 1
}
printf >&2 -- '%s' "$1"
exit ${2:-1}
}
EXAMPLE
die "Oops, there's something wrong!\n" 255
EXPLANATIONS
the first argument is the needed message, the second optional argument is the return code :
${2:-1} is a bash parameter expansion : it exit 1 if the second argument is missing
in shell, 1 is the same as FALSE (1 => 255)
in modern bash, die() { } is preferred as oldish function die {}
redirecting STDERR to STDOUT like Maxwell does, is not the best practice, instead, I redirect to STDERR directly (like perl does)
if you want to use it in interactive shell, put this in ~/.bashrc and then source ~/.bashrc
if you want to use it in scripts, you can source ~/.bashrc in your script or put it manually.
[[ $- == *i* ]] test whether you are in interactive shell or not

Resources