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"
echo "To generate it, run \"/usr/bin/env > $cron_env\" as a cron job"
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
env_string="/usr/bin/env -i "
for envi in $(cat "$cron_env"); do
env_string="${env_string} $envi "
done
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=//')
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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Owner 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.