Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Run a command or a script as cron would
#!/bin/bash
# Run as if it was called from cron, that is to say:
# * with a modified environment
# * with a specific shell, which may or may not be bash
# * without an attached input terminal
# * in a non-interactive shell
# This scripts supports cron jobs run by any user, just run it as the target user (e.g. using sudo -u <username>)
# An up-to-date version of this script may be available at https://gist.github.com/daladim/7f67eb95d59aadc8f3e8cd66c6f235d3
function usage(){
echo "$0 - Run a script or a command as it would be in a cron job, then display its output"
echo "Usage:"
echo " $0 [command | script]"
echo ""
echo "This scripts supports cron jobs run by any user."
echo "To do so, just run it as the target user (e.g. using sudo -u <username> $0)"
}
if [ "$#" -lt 1 -o "$1" == "-h" -o "$1" == "--help" ]; then
usage
exit 0
fi
# Depending on the distro, $HOME may or may not be affected by sudo.
# This is a better way to get the home directory of the current user
home=$( getent passwd "$(whoami)" | cut -d: -f6 )
cron_env="$home/.config/run-as-cron/cron-env"
function generate_env_file(){
# This function adds /usr/bin/env to a cron job and makes sure it is run only once
# i.e. it just helps the user who does not want to do it himself
echo -n "Adding a job to cron, to be run once... "
generator=$(mktemp /tmp/generator.cron.XXXX)
chmod 700 "$generator"
cat > "$generator" <<eof
#!/bin/bash
mkdir -p $(dirname "$cron_env")
/usr/bin/env > ${cron_env}
# Remove this script from the current cron jobs
crontab -l | sed "/$(basename $generator)/d" | crontab -
rm "$generator"
eof
# Add the just-created-script to cron
crontab -l | { cat; echo "* * * * * $generator"; } | crontab -
if [ "$?" -eq 0 ]; then
echo "Done at $(date)"
echo "It will be run shortly. You can try to run $0 again in a minute."
return 0
else
echo "Failed."
return 2
fi
}
# This file should contain the cron environment.
if [ ! -f "$cron_env" ]; then
echo "Unable to find $cron_env" >&2
echo "To generate it, run \"/usr/bin/env > $cron_env\" as a cron job" >&2
echo -n "Do you want this script to do it for you? [y/n] "
read reply
if [ "$reply" == "y" -o "$reply" == "Y" ]; then
generate_env_file
exit $?
fi
exit 1
fi
# It will be a nightmare to expand "$@" inside a shell -c argument.
# Let's rather generate a string where we manually expand-and-quote the arguments
# Note that may fail in case arguments (or environment variables) contain quotes...
env_string="/usr/bin/env -i "
while read -r line; do
env_string="${env_string} \"$line\" "
done < "$cron_env"
cmd_string=""
for arg in "$@"; do
cmd_string="${cmd_string} \"${arg}\" "
done
# Which shell should we use?
the_shell=$(grep -E "^SHELL=" "$cron_env" | sed 's/SHELL=//')
if [ -z "$the_shell" ]; then
echo "Unable to detect what shell should be used for this user." >&2
exit 2
fi
echo "Running with $the_shell the following command: $cmd_string"
# Let's route the output in a file
# and do not provide any input (so that the command is executed without an attached terminal)
so=$(mktemp "/tmp/fakecron.out.XXXX")
se=$(mktemp "/tmp/fakecron.err.XXXX")
"$the_shell" -c "$env_string $cmd_string" >"$so" 2>"$se" < /dev/null
echo -e "Done. Here is \033[1mstdout\033[0m:"
cat "$so"
echo -e "Done. Here is \033[1mstderr\033[0m:"
cat "$se"
rm "$so" "$se"
@poleguy
Copy link

poleguy commented Jul 13, 2020

This would be better if it would bootstrap the /usr/bin/env > /root/cron-env the first time it runs.

It also doesn't work if cron-env has a semicolor (';') in an environment variable, e.g.:
LS_COLORS=rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:.tar=38;5;9:.tgz=38;5;9:.arc=38;5;9:.arj=38;5;9:.taz=38;5;9:.lha=38;5;9:.lz4=38;5;9:.lzh=38;5;9:.lzma=38;5;9:.tlz=38;5;9:.txz=38;5;9:.tzo=38;5;9:.t7z=38;5;9:.zip=38;5;9:.z=38;5;9:.Z=38;5;9:.dz=38;5;9:.gz=38;5;9:.lrz=38;5;9:.lz=38;5;9:.lzo=38;5;9:.xz=38;5;9:.bz2=38;5;9:.bz=38;5;9:.tbz=38;5;9:.tbz2=38;5;9:.tz=38;5;9:.deb=38;5;9:.rpm=38;5;9:.jar=38;5;9:.war=38;5;9:.ear=38;5;9:.sar=38;5;9:.rar=38;5;9:.alz=38;5;9:.ace=38;5;9:.zoo=38;5;9:.cpio=38;5;9:.7z=38;5;9:.rz=38;5;9:.cab=38;5;9:.jpg=38;5;13:.jpeg=38;5;13:.gif=38;5;13:.bmp=38;5;13:.pbm=38;5;13:.pgm=38;5;13:.ppm=38;5;13:.tga=38;5;13:.xbm=38;5;13:.xpm=38;5;13:.tif=38;5;13:.tiff=38;5;13:.png=38;5;13:.svg=38;5;13:.svgz=38;5;13:.mng=38;5;13:.pcx=38;5;13:.mov=38;5;13:.mpg=38;5;13:.mpeg=38;5;13:.m2v=38;5;13:.mkv=38;5;13:.webm=38;5;13:.ogm=38;5;13:.mp4=38;5;13:.m4v=38;5;13:.mp4v=38;5;13:.vob=38;5;13:.qt=38;5;13:.nuv=38;5;13:.wmv=38;5;13:.asf=38;5;13:.rm=38;5;13:.rmvb=38;5;13:.flc=38;5;13:.avi=38;5;13:.fli=38;5;13:.flv=38;5;13:.gl=38;5;13:.dl=38;5;13:.xcf=38;5;13:.xwd=38;5;13:.yuv=38;5;13:.cgm=38;5;13:.emf=38;5;13:.axv=38;5;13:.anx=38;5;13:.ogv=38;5;13:.ogx=38;5;13:.aac=38;5;45:.au=38;5;45:.flac=38;5;45:.mid=38;5;45:.midi=38;5;45:.mka=38;5;45:.mp3=38;5;45:.mpc=38;5;45:.ogg=38;5;45:.ra=38;5;45:.wav=38;5;45:.axa=38;5;45:.oga=38;5;45:.spx=38;5;45:*.xspf=38;5;45:

This doesn'tget escaped right.

bash sucks.

@daladim
Copy link
Author

daladim commented Jul 13, 2020

@poleguy, thanks for your input.
I have just added a feature that bootstraps /usr/bin/env > /root/.config/run-as-cron/cron-env, just as you suggested.

I can see that you are trying (on your own fork) to fix this script when the environment contains semicolons. Feel free to tell me when/if you successfully test it, I'd be happy to include your improvements in this script!

@daladim
Copy link
Author

daladim commented Jul 17, 2020

@poleguy, I now have included your improvements in this gist.
I also have improved a few other things, added better error management, and this script now supports any user.

@nbonamy
Copy link

nbonamy commented Dec 13, 2021

Thanks for this! Updated the script: if no command is provided then crontab commands will be listed and user will be able to pick one. Available on https://gist.github.com/nbonamy/85ff67c6c1bb48120917d3935a4794aa

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment