Suppose I create my own script and I want that script to be the default command to be executed if command is not found, how do I go about configuring bash to make this happen
Eg. the command is called defCMD
Suppose I type some random string into the command line:
$ viosjaldfksajflsfa
Normally it would output 'viosjaldfksajflsfa not found'
However, instead of that happening I want 'viosjaldfksajflsfa' to be fed as parameter into the defCMD script I wrote and therefore have bash execute defCMD instead.
is there a way to make this happen?
My bash (Fedora) has the following
$ type command_not_found_handle
command_not_found_handle is a function
command_not_found_handle ()
{
runcnf=1;
retval=127;
[ ! -S /var/run/dbus/system_bus_socket ] && runcnf=0;
[ ! -x /usr/libexec/packagekitd ] && runcnf=0;
if [ $runcnf -eq 1 ]; then
/usr/libexec/pk-command-not-found $#;
retval=$?;
else
echo "bash: $1: command not found";
fi;
return $retval
}
You can start from here and add it to your .bashrc file. Change the else branch.
Related
I need to write a POSIX shell script that will change system configurations. Before doing so I want to ensure there are backups of any file I edit.
A requirement for this script is that is uses dmenu to prompt the user if installed and read if not.
I want one function (named communicate below) that will automatically handle this for me based on a variable that gets set on run, $dmenu.
I'm having issues writing to a variable inside a variable, as shown below:
#!/usr/bin/env sh
[ $(command -v dmenu 2>/dev/null) ] && dmenu='true'
communicate(){
description="$1"; options="$2"; outcome="$3"
if [ $dmenu ]; then
echo "$(printf "$options" | dmenu -i -p "$description")" >&0 | read $outcome
else
printf "$description $options "; read $outcome
fi
}
backup(){
[ $1 ] && file="$1" || communicate 'Enter file: ' '' 'file'
[ ! -f $file ] && backup "$1"
cp "$file" "$file.bak"
}
select_interface(){
[ $1 ] && interface="$1" || communicate 'Select interface:' "$interfaces" 'interface'
}
backup wants to save user input to a variable called $file, whereas later select_interface wants to save to a variable called $interface.
if dmenu is not installed, writing to $outcome works fine with the else statement, whereas if it is installed, I cannot seem to get the read command to trigger when passing the outcome of dmenu through with the STDIN redirect into read, which works outside of the script.
Can someone see what I'm doing wrong or how I could do this better?
I need it all to be in the one function communicate, acting as the communicating agent with the user.
The statement
echo "$(printf "$options" | dmenu -i -p "$description")" >&0 | read $outcome
being a pipe, causes the shell to implement echo and read as 2 separate processes. read is still a forked shell, and it still sets the variable $outcome, but it only sets it in the forked shell, not in the forking (parent) shell.
The technically correct way to do it is:
eval $outcome=\$\(printf "$options" \| dmenu -i -p "$description"\)'
BUT I would advise against eval for anything but throwaway code.
I also advise against functions which accept variable names to set, it's pretty hard to get right.
The cleaner way to do it:
#!/usr/bin/env sh
if [ $(command -v dmenu 2>/dev/null) ]; then
communicate() {
description="$1"
options="$2"
# also fixed this bug with the menu selection, each option needs to be in a new line
printf "%s\n" $options | dmenu -i -p "${description}:"
}
else
communicate() {
description="$1"
options="$2"
if [ -n "$options" ]; then
optstring="options: ${options}; "
else
optstring=""
fi
read -p "${optstring}${description}: " outcome
echo $outcome
}
fi
backup() {
if [ -n "$1" ]; then
file="$1"
else
file=$(communicate 'Enter file')
fi
if [ -f "$file" ]; then
cp "$file" "${file}.bak"
else
backup
fi
}
select_interface() {
if [ -n "$1" ]; then
interface="$1"
else
interface=$(communicate "Enter interface" "$interfaces")
fi
}
I am looking into how a particular exploit works, and I chose to look at one in the program 'chkrootkit' which allows for any user to run a malicious file as root. The source code for this vulnerable shellscript is as follows
slapper (){
SLAPPER_FILES="${ROOTDIR}tmp/.bugtraq ${ROOTDIR}tmp/.bugtraq.c"
SLAPPER_FILES="$SLAPPER_FILES ${ROOTDIR}tmp/.unlock ${ROOTDIR}tmp/httpd \
${ROOTDIR}tmp/update ${ROOTDIR}tmp/.cinik ${ROOTDIR}tmp/.b"a
SLAPPER_PORT="0.0:2002 |0.0:4156 |0.0:1978 |0.0:1812 |0.0:2015 "
OPT=-an
STATUS=0
file_port=
if ${netstat} "${OPT}"|${egrep} "^tcp"|${egrep} "${SLAPPER_PORT}">
/dev/null 2>&1
then
STATUS=1
[ "$SYSTEM" = "Linux" ] && file_port=`netstat -p ${OPT} | \
$egrep ^tcp|$egrep "${SLAPPER_PORT}" | ${awk} '{ print $7 }' |
tr -d :`
fi
for i in ${SLAPPER_FILES}; do
if [ -f ${i} ]; then
file_port=$file_port $i
STATUS=1
fi
done
if [ ${STATUS} -eq 1 ] ;then
echo "Warning: Possible Slapper Worm installed ($file_port)"
else
if [ "${QUIET}" != "t" ]; then echo "not infected"; fi
return ${NOT_INFECTED}
fi
}
I know that the reason the exploit works is because the line 'file_port=$file_port $i' will execute all files specified in $SLAPPER_FILES as the user chkrootkit is running (usually root), if $file_port is empty, because of missing quotation marks around the
variable assignment."
My question is why does the command
file_port=$file_port $i
result in execution of the file? Assuming that $i refers to the path of the file (/tmp/update)
I can see that file_port might be changed to some long netstat command in the previous if statement, is this something to do with it?
I've been trying to get my head around this all day to no avail, so at this point any help will be greatly appreciated :)
This is the one-shot variable assignment feature of any Bourne shell. Any command can be prefixed with zero or more variable assignments:
VAR1=VALUE1 VAR2=VALUE2 command arguments ...
Runs command arguments ... with the respective environment variables set for just this command. A typical use might be
EDITOR=vim crontab -e
I'm trying to learn the bash language. How do I execute a Linux command with a dynamic argument and check whether or not the returning string is empty. For example:
if ls "my_directory123" == `emtpy string` then
....
end
If you are testing for an empty directory pass in the first positional parameter "$1", you can test:
if test -z "$(ls -A "$1")" ; then
or
if [ -z "$(ls -A "$1")" ]; then
which are equivalent uses of the test of [ keywords.
Assign the result of the command to a variable and compare it as a string.
result=$(ls "my_directory123")
if [ "$result" = "" ]
then
echo empty
fi
TEST=`ls`
if [$TEST == ""]; then
echo "do something"
fi
if you want to run this in the terminal you key in each line at a time. If you prefer you can put everything in a file prepend with
#!/bin/bash
to make an executable string once you run
chmod +x FILENAME
Need to call Command Substitution and check for returned value. Some think like this
x=$(ls path)
if [ -z "$x" ] ;then echo empty ; else echo no empty; fi
I have bash script where i have echo before every command showing what is happening.
But i need to disbale echo when setting as cron job and then enable again if do some testing.
i find it very hard to go to each line and then add/remove comment
is there anything which i can include at top something like
enable echo or disable echo
so that i don't have to waste time
The absolute easiest would be to insert the following line after the hashbang line:
echo() { :; }
When you want to re-enable, either delete the line or comment it out:
#echo() { :; }
If you're not using echo but printf, same strategy, i.e.:
printf() { :; }
If you absolutely need to actually echo/printf something, prepend the builtin statement, e.g.:
builtin echo "This 'echo' will not be suppressed."
This means that you can do a conditional output, e.g.:
echo () {
[[ "$SOME_KIND_OF_FLAG" ]] && builtin echo $#
}
Set the SOME_KIND_OF_FLAG variable to something non-null, and the overridden echo function will behave like normal echo.
EDIT: another alternative would be to use echo for instrumenting (debugging), and printf for the outputs (e.g., for piping purposes). That way, no need for any FLAG. Just disable/enable the echo() { :; } line according to whether you want to instrument or not, respectively.
Enable/Disable via CLI Parameter
Put these lines right after the hashbang line:
if [[ debug == "$1" ]]; then
INSTRUMENTING=yes # any non-null will do
shift
fi
echo () {
[[ "$INSTRUMENTING" ]] && builtin echo $#
}
Now, invoking the script like this: script.sh debug will turn on instrumenting. And because there's the shift command, you can still feed parameters. E.g.:
Without instrumenting: script.sh param1 param2
With instrumenting: script.sh debug param1 param2
The above can be simplified to:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
fi
if you need the instrumenting flag (e.g. to record the output of a command to a temp file only if debugging), use an else-block:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
else
INSTRUMENTING=yes
fi
REMEMBER: in non-debug mode, all echo commands are disabled; you have to either use builtin echo or printf. I recommend the latter.
Several things:
Don't use echo at all
Instead use set -xv to set debug mode which will echo each and every command. You can set PS4 to the desired prompt: for example PS4='$LINENO: ' will print out the line number on each line. In BASH, I believe it's the same. Then, you don't have to clean up your script. To shut off, use set +xv.
Example:
foo=7
bar=7
PS4='$LINENO: '
set -xv #Begin debugging
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
set +xv #Debugging is off
if [ $bar = $foo ]
then
echo "And bar also equals foo"
fi
Results:
$ myprog.sh
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
5: [ 7 = 7 ]
7: echo 'foo certainly does equal bar'
foo certainly does equal bar
set +xv #Debugging is off
And bar also equals foo
Use a function
Define a function instead of using echo:
Example:
function myecho {
if [ ! -z "$DEBUG" ]
then
echo "$*"
fi
}
DEBUG="TRUE"
my echo "Will print out this line"
unset DEBUG
myecho "But won't print out this line"
Use the nop command
The colon (:) is the nop command in BASH. It doesn't do anything. Use an environment variable and define it as either echo or :. When set to a colon, nothing happens. When set to echo, the line prints.
Example:
echo=":"
$echo "This line won't print"
echo="echo"
$echo "But this line will."
Building on Matthew's answer, how about something like this:
myEcho = "/bin/true"
if [ ! "$CRON" ]: then
myEcho = "/bin/echo"
fi
and then use $myEcho instead of echo in your script?
You can do one better. If you setup your crontab as detailed in another answer, you can then check if you are running in cron and only print if you are not. This way you don't need to modify your script at all between different runs.
You should then be able to use something like this (probably doesn't quite work, I'm not proficient in bash):
if [ ! "$CRON" ]; then
echo "Blah blah"
fi
Try set -v at the top to echo each command. To stop echoing change it to set +v.
Not sure if I miss the below solution to use a variable (e.g. debug) at the start of the bash script.
Once you set the debug=true, any conditional-if will enable or disable multiple “echo statements” in bash script.
typeset debug=false # set to true if need to debug
...
if [ $debug == "true" ]; then
echo
echo "Filter"
read
fi
...
if [ $debug == "true" ]; then
echo
echo "to run awk"
fi
Couldn't post a code block in a comment, so I'll post this as an answer.
If you're a perfectionist (like I am) and don't want the last set +x line to be printed... and instead print Success or FAIL, this works:
(
set -e # Stop at first error
set -x # Print commands
set -v # Print shell input lines as they are read
git pull
// ...other commands...
) && echo Success || echo FAIL
It will create a sub process, though, which may be an overkill solution.
If you're running it in cron, why not just dump the output? Change your crontab entry so that it has > /dev/null at the end of the command, and all output will be ignored.
When I run the following script in Bash 3.2.48:
#!/bin/bash
export var1='var1'
echo "UID=$UID"
if [ x"$UID" != x"0" ]
then
export var2='var2'
while ! { sudo -v; }; do { sudo -v; }; done;
sudo $0
exit
fi
echo $var1
echo $var2
exit 0
What I get as output is:
UID=1000
UID=0
var1
Why is var2 not exported and echoed? I'm pretty sure that the same script worked with older Bash versions.
you enter first time with UID == 1000, you enter the if clause
you sudo to execute the script with UID == 0;
sudo doesn't preserve the environment if env_reset is set in /etc/sudoers (default in most distros). You need sudo -E to preserve env.
you exit (before echoing)
from the sudo call you enter with clean env.
you enter with UID == 0
you don't enter the if clause, var2 is not set
you echo the variables.
The answer is much more simpler than is seems: you never echo those vars (when not running as root, obviously), because you already exit :))
Try avoiding/minimizing confusion by adding more appropriate/concise debug statements. For instance, use a single echo that contains everything that's relevant to your problem (i.e. process ID, user ID, var1, var2):
#!/bin/bash
export var1='var1'
if [ "$UID" != "0" ] ; then
export var2='var2'
while ! { sudo -v; }; do { sudo -v; }; done;
sudo $0
# this is "the key exit" ;-)
#exit
fi
echo "pid=[$$] uid=[$UID] var1=[$var1] var2=[$var2]"
With the exit commented out you get what you expect (obviously, in the "parent" process, as the "child" one - the one running as "root" - never reaches that part of the code that exports var2):
pid=[12346] uid=[0] var1=[var1] var2=[]
pid=[12345] uid=[1] var1=[var1] var2=[var2]
++ sometimes running scripts in debug mode (bash -x) helps too ;-)