How can I intercept commands that contain a certain string? - bash

Sometimes, it happens to me that I'm executing certain commands, and only afterwards I realize that I sent the wrong parameter to a command ( like a restart of a Heroku application ). I'd like to modify bash in such a way that if it sees a command containing a certain string, it will prompt me whether I'm sure or not. For example ( imagine the string is tempus ):
$ heroku restart --app tempus
Now, I'd like bash to prompt me with a Y/N prompt, and if I type y, only then I'd like it to execute the command. If I type N, the command will not be executed. How could I handle this problem?

I don't know of any way to intercept all bash commands, but you can intercept predetermined commands using the following trick.
Create a directory (say ~/interception) and set it as the first entry in $PATH
Create the following script in that directory with a list of commands you wish to intercept and the full path to the actual command
[bash]$ cat intercept.sh
#!/bin/bash
# map commands to full path
declare -A COMMANDS
COMMANDS[heroku]=/usr/bin/heroku
COMMANDS[grep]=/bin/grep
# ... more ...
CMD=$(basename $0) # command used to call this script
if [[ ! -z "${COMMANDS[$CMD]+x}" ]]; then # mapping found
# Do what you wish here. You can even modify/inspect the params.
echo "intercepted $CMD command... "
${COMMANDS[$CMD]} $# # run actual command with all params
else
echo "Unknown command $CMD"
fi
In the same directory, create symlinks to that script using the name of the commands you wish to intercept
[bash]$ ln -s intercept.sh grep
[bash]$ ln -s intercept.sh heroku
Now, each time you call the command, that script is invoked via the symlink and it can then do your bidding before calling the actual command.
You can extend this further by sourcing the $COMMANDS from a config file and create helper commands to augment the config file and create/remove the sym links. You would then be able to manage the who setup using commands such as:
intercept_add `which heroku`
intercept_remove heroku
intercept_list

Because bash itself doesn't support command line filter, it's not possible to intercept commands.
Here is a dirty solution:
Find all executables in PATH and create wrapper functions for each of them.
The wrapper function then call prefilter() function if it's declared.
If prefilter() function failed, the command is canceled.
SOURCE: cmd-wrap.sh
#!/bin/bash # The shebang is only useful for debug. Don't execute this script.
function create_wrapper() {
local exe="$1"
local name="${exe##*/}"
# Only create wrappers for non-builtin commands
[ `type -t "$name"` = 'file' ] || return
# echo "Create command wrapper for $exe"
eval "
function $name() {\
if [ \"\$(type -t prefilter)\" = 'function' ]; then \
prefilter \"$name\" \"\$#\" || return; \
fi; \
$exe \"\$#\";
}"
}
# It's also possible to add pre/post hookers by install
# [ `type -t \"$name-pre\"` = 'function' ] && \"$name-pre\" \"\$#\"
# into the dynamic generated function body.
function _create_wrappers() {
local paths="$PATH"
local path
local f n
while [ -n "$paths" ]; do
path="${paths%%:*}"
if [ "$path" = "$paths" ]; then
paths=
else
paths="${paths#*:}"
fi
# For each path element:
for f in "$path"/*; do
if [ -x "$f" ]; then
# Don't create wrapper for strange command names.
n="${f##*/}"
[ -n "${n//[a-zA-Z_-]/}" ] || create_wrapper "$f"
fi
done
done
unset _create_wrappers # Remove the installer.
unset create_wrapper # Remove the helper fn, which isn't used anymore.
}
_create_wrappers
To utilize it for your problem:
source it in bash:
. ./cmd-wrap.sh
Create your version of prefilter() to check if any argument contains the string:
function prefilter() {
local a y
for a in "$#"; do
if [ "$a" != "${a/tempus}" ]; then
echo -n "WARNING: The command contains tempus. Continue?"
read y
[ "$y" = 'Y' ] || [ "$y" = 'y' ]
return $?
fi
done
return 0
}
Run
heroku restart --app tempus
but not
/usr/bin/heroku restart --app tempus
to make use of the wrapper function.

The easiest way is to use aliases. This simple example should work for you:
This protects from executing the heroku command with tempus in the arguments
function protect_heroku {
# use grep to determine if the bad string is in arguments
echo "$*" | grep tempus > /dev/null
# if string is not in arguments
if [ $? != 0 ]; then
# run the protected command using its full path, so as not to trigger alias
/path/to/heroku "$#"
else
# get user confirmation
echo -n Are you sure \(y/n\)?' '
read CONFIRM
if [ "$CONFIRM" = y ]; then
# run the protected command using its full path
/path/to/heroku "$#"
fi
fi
}
# This is the key:
# This alias command means that 'heroku' from now refers
# to the function protect_heroku, rather than /bin/heroku
alias heroku=protect_heroku
Put this code into your bash profile ~/.profile and then log out and log back in. From now on, bash will protect you from accidentally running heroku with tempus.

Simplest way is to replace heroku with a script that does the checking before executing the real heroku. Another way would be to add a bash alias for heroku.

Related

Run a bash command from a string

I have this function named st that let's me change the name of the terminal so that I understand what the terminal session is doing.
function st() {
if [[ -z "$ORIG" ]]; then
ORIG=$PS1
fi
TITLE="\[\e]2;$*\a\]"
PS1=${ORIG}${TITLE}
}
So when I run st yarn transportation start the title of my terminal changes to yarn transportation start. So far so good.
I don't know much about bash.
But it pains me that I have to now run yarn transportation start to actually run that command. Is there any way that I can run anything after st after the title is changed?
I want to run a single command that changes the title of the terminal and runs the command that I gave in the title
After setting title (setting PS1), you need to make sure that right TITLE is being used so that proper command can be run.
As pointed in How to execute a bash command stored as a string with quotes and asterisk there is eval function that runs the command from a string.
$ type st
type st
st is a function
st ()
{
if [[ -z "$ORIG" ]]; then
ORIG=$PS1;
fi;
TITLE="\[\e]2;$*\a\]";
PS1=${ORIG}${TITLE};
TITLE="$*"; # Take all the arguments
eval $TITLE
}
You can even at a -t flag to provide a separate title and command like below
function st() {
TITLE=$*
COMMAND=$*
if [[ -z "$ORIG" ]]; then
ORIG=$PS1
fi
if [ "$1" == "-t" ]; then
TITLE=$2;
COMMAND=${#:3}
fi
TITLE="\[\e]2;$TITLE\a\]"
PS1=${ORIG}${TITLE}
eval $COMMAND
}

How can I export function in a function in bash?

I am learning bash. And I would like to make a function which wrap another function in a temporal script file and execute it with sudo -u command in sub-shell.
The problem I encountered is the generated script cannot find the wrapped function although it is exported in the wrap function.
I append test cords below. Someone who finds problems, please let me know. Thank you very much.
main.sh
source "./display.sh"
source "./sudo_wrap.sh"
display_func "load success"
sudo_wrap_func '' 'display_func' '3' '4 5'
output, display.sh, sudo_wrap.sh and generated temporal file are appended below,
output
display_func : load success
export -f display_func
30481: line 5: display_func: command not found
display.sh
function display_func() {
echo "display_func : $#"
}
sudo_wrap.sh
function sudo_wrap_func() {
local sudo_user="${1:-root}"
local function_name="${2:?'function_name is null string.'}"
shift 2
local func_augs=( "$#" )
local script
# *** script : header ***
script="#!/bin/bash\n"
script="${script}\n"
# *** script : making augments for function ***
script="${script}augs=("
for aug in "${func_augs[#]}"
do
if [[ "${aug}" =~ [[:blank:]] ]]; then
script=" ${script} \"${aug}\""
else
script=" ${script} ${aug}"
fi
done
script="${script})\n"
local tmp_script_file="${RANDOM}"
echo -e "${script}" >> "${tmp_script_file}"
# *** script : calling function with augments ***
echo -e "${function_name} \"\${augs[#]}\"\n" >> "${tmp_script_file}"
echo "export -f "${function_name}"" >&2
export -f "${function_name}"
sudo -u"${sudo_user}" bash "${tmp_script_file}"
rm "${tmp_script_file}"
}
temporally generated file (in this case, file name is 30481)
#!/bin/bash
augs=( 3 "4 5")
display_func "${augs[#]}"
As I said in a comment, the basic problem is that sudo cleans its environment (including both variables and functions) before running the command (/script) as another user. This can be overridden with sudo -E, but only if it's explicitly allowed in /etc/sudoers.
But the problem is not insoluble; you just have to include the definition of the function in the script, so it gets recreated in that environment. bash even has a convenient command, declare -f display_func, that prints the function definition in the appropriate form (and declare -p variable does the same for variables). So you can use those to add the appropriate definitions to the script.
Here's a script I wrote to do this. I made a few other changes vs. your script: I take -u username to specify a different user to run as (so you don't have to pass '' as the first argument if you don't want to specify a different user). I also added -f functionname and -v variablename to "export" additional function and variable definitions into the script (in case the main function depends on them). I also create the temp script file in /tmp, and change ownership if necessary so it'll be readable by the other user.
#!/bin/bash
me="$(basename "$0")"
usage() {
echo "Usage: $me [-u user] [-f otherfunction] [-v variablename] function [args...]" >&2
}
tmp_script_file=$(mktemp "/tmp/${me}.XXXXXXXXXXXX") || {
echo "Error creating temporary script file" >&2
exit 1
}
echo "#!/bin/bash" > "$tmp_script_file" # Not actually needed, since we'll run it with "bash"
# Parse the command options; "-u" gets stored for later, but "-f" and "-v" write
# the relevant declarations to the script file as we go.
sudo_user=""
while getopts u:f:v: OPT; do
case "$OPT" in
u)
sudo_user="$OPTARG" ;;
f)
declare -f "$OPTARG" >>"$tmp_script_file" || {
echo "Error saving definition of function $OPTARG" >&2
exit 1
} ;;
v)
declare -p "$OPTARG" >>"$tmp_script_file" || {
echo "Error saving definition of variable $OPTARG" >&2
exit 1
} ;;
?) usage; exit 1 ;;
esac
done
shift $(($OPTIND-1))
if (( $# == 0 )); then # No actual command specified
usage
exit 1
fi
# Write the main function itself into the script
declare -f "$1" >>"$tmp_script_file" || {
echo "Error saving definition of function $1" >&2
exit 1
}
# Then the command to run it, with arguments quoted/escaped as
# necessary.
printf "%q " "$#" >>"$tmp_script_file"
# the printf above won't write a newline, so add it by hand
echo >>"$tmp_script_file"
# If the script will run as someone other than root, change ownership of the
# script so the target user can read it
if [[ -n "$sudo_user" ]]; then
sudo chown "$sudo_user" "$tmp_script_file"
fi
# Now launch the script, suitably sudo'ed
sudo ${sudo_user:+ -u "$sudo_user"} bash "$tmp_script_file"
# Clean up
sudo rm "$tmp_script_file"
Here's an example of using it:
$ foo() { echo "foo_variable is '$foo_variable'"; }
$ bar() { echo "Running the function bar as $(whoami)"; echo "Arguments: $*"; foo; }
$ export -f foo bar # need to export these so the script can see them
$ export foo_variable='Whee!!!' # ditto
$ # Run the function directly first, so see what it does
$ bar 1 2 3
Running the function bar as gordon
Arguments: 1 2 3
foo_variable is 'Whee!!!'
$ # Now run it as another user with the wrapper script
$ ./sudo_wrap.sh -f foo -v foo_variable -u deenovo bar 1 2 3
Running the function bar as deenovo
Arguments: 1 2 3
foo_variable is 'Whee!!!'
Note that you could remove the need to export the functions and variables by either running the script with source or making it a function, but doing that would require changes to how $me is defined, the usage function, replacing all those exits with returns, and maybe some other things I haven't thought of.

creating alias for a bash script with a wild card option in the argument

I wanted to create the alias for the a bash script that make use of wild card as an argument. When I am trying to make use of the alias cmd its not giving the required output.
The usage of the alias cmd will be log_list /tmp/abc*
#usage log_list /tmp/abc*
alias log_list=`sh scriptname $1`
#here is the script code
for file_name in $* ; do
if [ ! -d $file_name ] && [ -f $file_name ] ; then
#do some processing ...
echo $file_name
fi
done
Aliases don't handle arguments via $1 and the like; they just prepend their text directly to the rest of the command line.
Either use:
alias log_list='sh scriptname' # note that these are single-quotes, not backticks
...or a function:
log_list() { sh scriptname "$#"; }
...though if your script is named log_list, marked executable, and located somewhere in the PATH, that alias or function should be completely unnecessary.
Now, that said, your proposed implementation of log_list also has a bunch of bugs. A cleaned-up version might look more like...
#!/bin/sh
for file_name in "$#" ; do
if [ ! -d "$file_name" ] && [ -f "$file_name" ] ; then
#do some processing ...
printf '%s\n' "$file_name"
fi
done

Is it possible to make some shell calls automatically when entering a directory?

Just like a local .bashrc file, which is sourced every time I entered the directory.
How to make this work?
You can use an alias:
$ echo 'echo "execute something for $PWD"' > tests/.cdrc
$ _cd()
{
\cd $1
[ -r .cdrc ] && . .cdrc
}
this function first change to the dir specified as argument, check if the file .cdrc is readable and source it.
$ alias cd=_cd
Then
$ cd tests
execute something for /path/to/tests
bash and zsh (and probably many other shells) have a feature that allows you to run an arbitrary command before the prompt is displayed. You can use this to source a .dirrc file, and it won't break tab completion.
Here's how to do it in bash:
PROMPT_COMMAND='
if [ "${PREV}" != "$(pwd -P)" ]; then
if [ -r .dirrc ]; then
. ./.dirrc
fi
PREV=$(pwd -P)
fi
'
From the bash man page:
PROMPT_COMMAND: If set, the value is executed as a command prior to issuing each primary prompt.
This is how to do it in zsh (see the zshmisc man page):
precmd() {
if [ "${PREV}" != "$(pwd -P)" ]; then
if [ -r .dirrc ]; then
. ./.dirrc
fi
PREV=$(pwd -P)
fi
}

ZSH which/whence alias for type -p not identifying node

We have code to check for a node install:
which="type -p"
if [ $SHELL = "/bin/zsh" ]; then
which="whence"
fi
# make sure that node exists
node=`$which node 2>&1`
ret=$?
if [ $ret -ne 0 ] || ! [ -x "$node" ]; then
<"This error code is returned">
But when I run this with ZSH (OhMyZsh) it returns a 127 (does not exist). Commenting out the which="whence" lets it run fine.
Without removing the whole aliasing bit is there any way to have ZSH play along with this? Ideally I'd like to make a change on my end to make this work rather than modifying this code at all.
You mean, you run $node and it appears that you’ve tried to run command whose name is node --alias-args which does not exist?
If this is true, change the third line to use whence -p: it has the same output as type -p in bash. If not, please, explain when this code is returned.
Update: I do not know what was done in ohmyzsh (though I have not a single idea how to make a builtin not found) so just try to rewrite the code in this way:
# At the very top
if [ -n $ZSH_VERSION ] ; then
emulate -L zsh
endif
<...>
which="type -p"
if [ -n $ZSH_VERSION ] ; then
which=( whence -p ) # Changes variable type as well
endif
node=`$which node 2>&1`
if (( ? )) || ! test -x $node ; then
<...>

Resources