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.
Related
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.
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 ([[...]]).
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
Some commands have many -x (x can be any English letter) options and it's some times difficult to remember all of their meanings. I can use bash's compgen -W '-a -b -c' to show possible options and I'm wondering if it's possible to also show some help message. Like this:
bash# foo -<TAB><TAB>
-a: This is option a -b: This is option b
-C: This is option c
bash#
I ever did something similar to map some of curl's single char options (like -x) to GNU style --long-options.
This is how it works:
[STEP 101] # cat curl
function _compgen_curl()
{
local cmd=$1 cur=$2 pre=$3
local -a options=( \
'' --connect-timeout \
-k --insecure \
-m --max-time \
-o --output \
-O --remote-name \
-u --user \
-U --proxy-user
-x --proxy \
-y --speed-time \
-Y --speed-limit \
)
local -a options2=()
local i short long
for ((i = 0; i < ${#options[#]}; i += 2)); do
short=${options[i]}
long=${options[i+1]}
if [[ -z $short || -z $long ]]; then
options2+=( $short$long )
else
options2+=( $short,$long )
fi
done
if [[ $cur == - ]]; then
COMPREPLY=( $( compgen -W "${options2[*]}" -- "$cur" ) )
elif [[ $cur == --* ]]; then
COMPREPLY=( $( compgen -W "${options[*]}" -- "$cur" ) )
fi
}
complete -F _compgen_curl -o bashdefault -o default curl
[STEP 102] # . ./curl
[STEP 103] # curl -<TAB><TAB>
--connect-timeout -o,--output -u,--user -y,--speed-time
-k,--insecure -O,--remote-name -x,--proxy
-m,--max-time -U,--proxy-user -Y,--speed-limit
[STEP 103] # curl -
Not exactly what you asked but you can update it for your own purpose.
(I'm not sure if bash can handle whitespaces in the completion result but at least you can use _ or -. :-)
IMO that would be a bad idea.
But if you really want it, you can do it something like this:
Note that this code is a bit crude and unless I know your use case, I cannot provide exact answer. However, it should be sufficient to give you a general idea.
Original:
complete -o nospace -F _default_completion my_command
New:
_custom_completion(){
local cur;
_get_comp_words_by_ref cur;
_default_completion
if [ ${#COMPREPLY[#]} == 1 ]; then return; fi
local _compreply=()
local reply_entry
local description
for reply_entry in ${COMPREPLY[#]}; do
description=$(generate_description_from_option "$reply_entry")
description=$(printf "%${COLUMNS}s" "$reply_entry : $description" )
_compreply+=$description
done
COMPREPLY=(${_compreply[#]})
} && complete -o nospace -F _custom_completion my_command
With this, bash should show one option per line with description in front of it. Of course, you will need to write generate_description_from_option yourself.
ble.sh provides that, besides many other features.
During the configuration of Symfony 2 project it is required to set appropriate privilages to the cache and log directories.
Documentation says to do it in two ways. One of them is calling setfacl command with -m modificator. However not every version contains this modificator. Is it possible to check if this command or any other command allows to set some modificator ?
For example with following pseudocode:
if [ checkmods --command=setfacl --modificator=-m ]
setfacl -m ....
else
chmod ...
You can parse the usage information by running setfacl --help and check if contains the modificator. For example:
if setfacl --help | grep -q -- -m,
then
echo "setfacl -m supported"
else
echo "setfacl -m not supported"
fi
If you want to do it for any command which has the --help option, take a look at the _parse_help function available in your bash-completion file.
http://anonscm.debian.org/gitweb/?p=bash-completion/bash-completion.git;a=blob;f=bash_completion
# Parse GNU style help output of the given command.
# #param $1 command; if "-", read from stdin and ignore rest of args
# #param $2 command options (default: --help)
#
_parse_help()
{
eval local cmd=$( quote "$1" )
local line
{ case $cmd in
-) cat ;;
*) LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 ;;
esac } \
| while read -r line; do
[[ $line == *([ $'\t'])-* ]] || continue
# transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc
while [[ $line =~ \
((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do
line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}
done
__parse_options "${line// or /, }"
done
}