sh conditionally pass an option to command - bash

I want to do something like:
#!/bin/sh
[ -f "/tmp/nodes" ]
[[ $? -eq 0 ]] && VAL=$? ||
geth --datadir /root/.ethereum \
${VAL+"--nodekey \"/root/nodekey.txt\""} \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
I want the option --nodekey "/root/nodekey.txt" to be passed if the file /tmp/nodes exists. How can that be done more elegantly than an if with two nearly identical commands?
--EDIT--
This is the best I've been able to get working so far:
if [ $VAL -eq 0 ]; then
/geth --datadir /root/.ethereum \
--nodekey "/root/nodekey.txt" \
# No dice
# Would be nice if this worked so I didn't need the if
# ${VAL+ --nodekey "/root/nodekey.txt" } \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0"
else
/geth --datadir /root/.ethereum \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
fi
This is another line in the file and works fine:
ENODE_URL=$(/geth --datadir /root/.ethereum ${VAL+ --nodekey "/root/nodekey.txt"} --exec "${JS}" console 2>/dev/null | sed -e 's/^"\(.*\)"$/\1/')

There's a bashism here, but it's [[ $? -eq 0 ]], as [[ is a ksh extension adopted by bash. There's no point to using $? at all here, since you can just directly perform your assignment based on whether the test -f succeeds:
touch /tmp/nodes # set us up for the truthy path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
...properly emits as output (as run with dash, perhaps the most common minimal /bin/sh interpreter):
/tmp/nodes
REALLY EXISTS
(yes, really)
By contrast, to demonstrate that the other path fails as it should:
rm -f -- /tmp/nodes # set us up for the falsey path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
emits as output only:
/tmp/nodes

Related

How to make one loop out of five in bash?

I have a small script for Mac where I'm adding printers. It works fine but I think I could make it simpler or at least it would be interesting to know a different solution.
while IFS= read -r line;
do
if [[ $line == *"Printer_E1"* ]]
then
if [[ "$FIND_PRINTERS" =~ "$PRINTER_E1_IP" ]];
then
echo "found printer e1"
else
echo "adding printer e1"
"$LPADMIN" -p "$PRINTER_E1_IP" -v "lpd://$PRINTER_E1_IP" -L "$PRINTER_E1_LOCATION" -P "$PRINTER_E1_PPD" -E -o printer-is-shared=false -D "$PRINTER_E1_NAME"
echo "adding printer e1 done"
fi
fi
done <<< "$AD_GROUPS"
The content of $AD_GROUPS is:
Printer_E0
Printer_E1
Printer_E2
Printer_E3
Printer_E4
Printer_Strasse
Printer_Wien
I have such a loop for 5 printers, so 5 times that just with different variables.
How could I do that with one loop? (or how can I make that different or simpler)?
Something like this:
while IFS= read -r printer; do
[[ "$FIND_PRINTERS" =~ "${printer}_IP" ]] && \
echo "Found ${printer}" && continue
echo "Adding ${printer}..."
"$LPADMIN" -p "${printer}_IP" \
-v "lpd://${printer}_IP" \
-L "${printer}_LOCATION" \
-P "${printer}_PPD" -E -o printer-is-shared=false \
-D "${printer}_NAME" \
&& echo "Done"
done <<< "$AD_GROUPS"
I assume your variable FIND_PRINTERS has some printers you want to skip, that you have already set the parameters (IP, LOCATION etc) related to each printer.
We use the variable inside double quotes into there, so it expands to what you want for the various commands. Also I have simplified the if condition then command to condition && command and also continue moves to next iteration.

Unable to execute command inside double square brackets inside my Makefile

clean:
#for container_name in ${NEW_DJANGO_IMAGE_NAME} \
${NEW_MSQL_IMAGE_NAME} \
${NEW_NGINX_IMAGE_NAME} \
${NEW_REDIS_IMAGE_NAME}; \
do if [[ 'a' == 'a' ]]; then echo 'fdfdf'; fi; done;
If I do something like this it works. Now instead of this silly line
do if [[ 'a' == 'a' ]]; then echo 'fdfdf'; fi; done;
I want to write the following:
do if [[ docker ps --filter "name=^/$$container_name$$" --format '{{.Names}}' == $$container_name ]]; then echo 'fdfdf'; fi; done;
The idea is that I iterate over a number of docker containers and if it happens that some of them are running I want to stop them. So in the place of echo 'fdfdf' I want to see this line: docker container stop <CONTAINER_NAME>;
Looks as simple as hell but I can't get it to work in the Makefile...What am I doing wrong?
You apparently think that [[ cmd == "string" ]] executes cmd before performing the test. This is not the case. Use:
[[ `cmd` == "string" ]]
instead. In your case it would look like this:
do if [[ `docker ps --filter "name=^/$$container_name$$" --format '{{.Names}}'` == $$container_name ]]; then docker container stop $$container_name; fi; done;
Or, a bit more readable, maybe:
IMAGES := $(NEW_DJANGO_IMAGE_NAME) $(NEW_MSQL_IMAGE_NAME) \
$(NEW_NGINX_IMAGE_NAME) $(NEW_REDIS_IMAGE_NAME)
clean:
#for cn in $(IMAGES); do \
tmp=`docker ps --filter "name=^/$$cn$$" --format '{{.Names}}'`; \
if [ -n "$$tmp" ]; then \
docker container stop $$cn; \
fi; \
done
Note that, in this last version, we use the bourne shell test commmand ([) instead of the bash-only conditional expression ([[...]]).

Makefile multiline dash command run executable in a detached process

I have the following target in my makefile: (I'd like to run python http server in a detached process and when bash script is done kill the server)
TEST_PORT = 17777
test::
$(ENV_VARS) \
python -m SimpleHTTPServer $(TEST_PORT); \
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); \
echo $(PID); \
if [ -n "$$PID" ]; \
then \
python test.py; \
fi; \
function finish { \
if [ -n "$$PID" ]; \
then \
kill -9 $$PID; \
fi \
} \
trap finish EXIT;
However when I put a & after the line python ... I get an error
/bin/dash: Syntax error: ";" unexpected
How can this be done in a proper way?
EDIT
I have changed my makefile to do the following:
test::
python -m SimpleHTTPServer $(TEST_PORT) &
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); \
if [ -n "$$PID" ]; \
then \
$(ENV_VARS) python test.py; \
fi \
function finish { \
if [ -n "$$PID" ]; \
then \
kill -9 $$PID; \
fi \
} \
echo $$PID; \
trap finish EXIT;
However I am getting an error: (without the line number)
/bin/dash: Syntax error: word unexpected
The important thing to remember here is that your line breaks don't actually exist when the shell sees the command.
So your first command becomes:
$(ENV_VARS) python -m SimpleHTTPServer $(TEST_PORT); PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); echo $(PID); if [ -n "$$PID" ]; then python test.py; fi; function finish { if [ -n "$$PID" ]; then kill -9 $$PID; fi } trap finish EXIT;
And your second command becomes:
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); if [ -n "$$PID" ]; then $(ENV_VARS) python test.py; fi function finish { if [ -n "$$PID" ]; then kill -9 $$PID; fi } echo $$PID; trap finish EXIT;
Now those are both very hard to read so I don't expect you to spot the problem but the problem is that you are missing statement terminators in a few places.
Specifically:
Braces ({}) are word elements and so need spaces around them (and a terminator before, and after, the closing brace). You are missing those terminators here fi } trap and here fi } echo.
fi is also not a statement terminator and so it needs one between it and the next statement. You are missing one here test.py; fi function (as well as the ones in the braces from the first point).

How to securely quote an optional flag?

If $FOO is set, I want to run:
cd "$OUTPUTDIR" && fpm -s dir -t rpm \
-a x86_64 \
--epoch "${PKGEPOCH}" \
-n "${PACKAGENAME}" \
--version "${PKGVERSION}" \
--iteration "${PKGRELEASE}" \
-C "$OUTPUTDIR/installroot" \
--description="${PKGDESCRIPTION}" \
.
If $FOO is not set, I don't want to include the flag at all.
The program fails if --description= (empty).
However, sometimes descriptions include quotes and other special characters so I don't want to do:
if [[ -z "PKGDESCRIPTION" ]]; then
D=--description="${PKGDESCRIPTION}"
fi
cd "$OUTPUTDIR" && fpm -s dir -t rpm \
-a x86_64 \
--epoch "${PKGEPOCH}" \
-n "${PACKAGENAME}" \
--version "${PKGVERSION}" \
--iteration "${PKGRELEASE}" \
-C "$OUTPUTDIR/installroot" \
$D
.
If I put quotes around $D then it becomes an additional (blank) arg.
Is there a way to do this that won't be a security problem if $PKGDESCRIPTION includes special chars AND doesn't generate a blank arg?
Using an array is the only sane way to do this:
options=( -a x86_64 -C "$OUTPUTDIR/installroot" )
[[ $PKGEPOCH ]] && options+=( --epoch "$PGKEPOCH" )
[[ $PACKAGENAME ]] && options+=( -n "$PACKAGENAME" )
[[ $PKGVERSION ]] && options+=( --version "$PKGVERSION" )
[[ $PKGRELEASE ]] && options+=( --iteration "$PKGRELEASE" )
[[ $PKGDESCRIPTION ]] && options+=( --description="$PKGDESCRIPTION" )
cd "$OUTPUTDIR" && fpm -s dir -t rpm "${options[#]}"
See also http://mywiki.wooledge.org/BashFAQ/050
If PKGDESCRIPTION is the only argument that needs this conditional treatment, you can use an "alternate value" expansion:
[...] && fpm -s dir -t rpm \
[...] \
${PKGDESCRIPTION:+ --description="${PKGDESCRIPTION}"} \
.
Explanation: the :+ means this will expand to nothing at all unless PKGDESCRIPTION is set to a non-null value; if it is set to something non-null, it expands to --description="${PKGDESCRIPTION}", and the double-quotes make it ignore special characters in PKGDESCRIPTION's value. Note that the space in :+ -- isn't needed, but does no harm and makes it at least slightly easier to read.
BTW, if more than one argument needs this treatment, I'd go with #glenn jackman's approach.

makefile setting a variable in if statement in a target

I have a Makefile with the following:
AVAR=""
all :
if [ -d ../old ]; then \
(echo "$# Ping!"; AVAR="../old"; echo $(AVAR)) \
fi
#echo $(AVAR)
The idea is that depending on the presence of directory "../old" i will or not
have information in AVAR (available for use later), however, the if is evaluating to true, and the Ping! is echoed, but nothing is assigned to AVAR, either inside or outside the if statement.
Output is as follows:
$ make all
if [ -d ../old ]; then \
(echo "all Ping!"; AVAR="../old"; echo "") \
fi
all Ping!
Any Insight appreciated.
1) Each command in a makefile recipe runs in its own subshell. You define AVAR in the first command (the "if" statement), so it's not available to the second command (#echo $(AVAR)).
2) Within the first command, you define AVAR, but you don't call it right. The term $(AVAR) is Make syntax; Make expands it before executing the command, and since Make doesn't know anything about such a variable, it expands to nothing. You have to use the shell to expand it, using shell syntax: $AVAR. But if you try that, Make will expand the $A to nothing, and you'll get "VAR". So you "escape" the $ with another $:
all :
if [ -d ../old ]; then \
(echo "$# Ping!"; AVAR="../old"; echo $$AVAR) \
fi
Try
(echo "$# Ping!"; AVAR="../old"; echo $$AVAR) \
instead of
(echo "$# Ping!"; AVAR="../old"; echo $(AVAR)) \
That may be a bit late, but in case someone else has the same question.
The way I handle it is by evaluating the if in a shell function and extract the stdout to a variable that's usable in the next command.
Method 1: appending the echo to the AVAR setting
if [ -d ../old ]; then \
(echo "$# Ping!") \
fi
AVAR=$(shell if [ -d ../old ]; then \
(AVAR="../old"; echo $${AVAR}) \
fi;) && \
echo $${AVAR}
or Method 2: evaluating the variable so that it could be used throughout the target without appending the echo ${AVAR} to the same shell.
if [ -d ../old ]; then \
(echo "$# Ping!") \
fi
$(eval AVAR=$(shell if [ -d ../old ]; then \
(AVAR="../old"; echo $${AVAR}) \
fi;))
#echo ${AVAR}
instead of
if [ -d ../old ]; then \
(echo "$# Ping!"; AVAR="../old"; echo $(AVAR)) \
fi
#echo $(AVAR)
Notes:
- AVAR being set by the $(shell ... ) output is a different variable from the one inside the if condition
- If you want to use a single if, you'll have to remove the stdout of "$# ping" from the beginning of AVAR content
Problem solved, Did the very bad recursive make option to redefine stuff and reinterpret.

Resources