I am writing a sed command that should uncomment an entry in crontab. Is there a better way to do this? The first option that comes to mind is sed.
Example:
Sed is great for matching specific regular expressions and manipulating text in certain ways, but this doesn't seem to me to be one of them. While you can use sed for this task, the result is perhaps overly complex and fragile.
Your initial attempt was:
sed "s#\# 5 * * * 3 bash test.sh#5 * * * 3 bash test.sh#"
This fails because the *
character is a special character within your regular expression, and is translated as "zero or more of the previous atom" (in this case, a space). Strictly speaking, you may get this sed script to work by escaping the asterisks in your regex (not required in your replacement pattern).
But this only helps for this specific pattern. What if one of your co-workers decides to run this script at 6 minutes after the hour instead of 5, in order to avoid conflict with another script? Or there's a space after the comment character? Suddenly your sed substitution fails.
To uncomment out every commented occurrence of the script in question, you might use:
crontab -l | sed '/# *\([^ ][^ ]* *\)\{5\}[^ ]*test\.sh/s/^# *//' | crontab -
If you're using a more modern sed, you could replace this BRE with a slightly shorter ERE:
crontab -l | sed -E '/# *([^ ]+ *){5}[^ ]*test\.sh/s/^# *//' | crontab -
This takes the output of crontab -l
, which is obviously your complete crontab, manipulates it with sed, and then writes a new crontab based on its output using crontab -
. The sed script matches searches for lines matching what looks like a valid crontab (to avoid actual comments that simply mention your script), then does a simple substitution to remove only the comment character at the start. The matched pattern breaks out like this:
# *
- Matches the comment character followed by zero or more spaces([^ ]+ +){5}
- five non-space strings, followed by spaces[^ ]*
- Any number of non-space characters, which lead up to:test\.sh
- your script.Note, however, that this doesn't match all valid crontab times, which might include tags like @reboot
, @weekly
, @midnight
, etc. Check out man 5 crontab
for details.
A non-sed alternative like awk
might be in order. The following awk solution makes more sense to me:
crontab -l | awk -v script="test.sh" '
{ field=6 }
/^# / { field++ }
index($field,script) { sub(/^#/,"") }
1' \
| crontab -
While it's just a little longer, I find it easier to read and understand.