0

I have a variable which contains a line from a crontab

eg:

0 22 * * 1-5 echo hello

I want to split this into 2 variables, one with the numbers or asterisks denoting the periodicity of the job, and one with the command to be executed when the job runs.

So i need to find a way to split the string the first time an alphabetic character occurs, but i have no idea how to do this.

  • 3
    Beware that some cron implementations support names for the month and day of week... – Jeff Schaller Nov 8 at 11:07
2

This is not exactly what you're looking for, but should work for your use case. Read the line into six variables split by the space and tab character defined by the IFS variable.

Example with modified crontab entry:

$ line='0   22  * * 1-5 echo "foo  bar   baz"'
$ IFS=$'\t ' read -r a b c d e cmd <<<"$line"
$ echo "$a $b $c $d $e"
0 22 * * 1-5
$ echo "$cmd"
echo "foo  bar   baz"

Note that multiple spaces (or tabs) in the first five fields are squeezed to one space character, but spaces/tabs in the command stay intact. You can assign "$a $b $c $d $e" to a single variable if you need to.

  • You'd want read -r to preserve any backslashes. – glenn jackman Nov 8 at 12:46
  • Yes I do, thank you! – Freddy Nov 8 at 15:19
1

This can be done just with Shell Parameter Expansion and Pattern Matching

$ line='0 22 * * 1-5 echo hello'
$ prefix=${line%%[[:alpha:]]*}     #(a)
$ echo "$prefix"
0 22 * * 1-5 
$ suffix=${line#"$prefix"} .       #(b)
$ echo "$suffix"
echo hello

a: from the end, find the longest substring matching an alpha followed by any character, and remove it from $line

b: from the start, remove the prefix test from $line.

  • Interesting, thanks. However ive realised that since the command will usually contain non alphanumeric characters, this probably wont work for real examples.. Is there a way i could split the variable into an array by spaces from the beginning, but leave the rest of the line intact after the 5th space? Sorry im very new to all this :) – David Robert Dobes Nov 8 at 4:55
  • This is not a regular expression, it is a glob pattern. [[:alpha:]]* means "one alpha character followed by zero or more of any character". – glenn jackman Nov 8 at 12:45
0

I think you could do this using awk, in particular since your tags indicate you want to use this in a shell script. Take a look at the following code snippet, where I will, as the other repliers, for sake of the argument assign a bash variable with the content you are likely to process:

#!/bin/bash

# fill 'line' with example content
line='0   22  * * 1-5 echo foo  bar   baz -o outfile.txt *'

# split 'line' into "scheduling" and "command" part:
SCHED=$(awk '{match($0, "^([0-9* -]*)([[:print:]]*)", a); print a[1]}' <<< "$line")
echo "$SCHED"
CMD=$(awk '{match($0, "^([0-9* -]*)([[:print:]]*)", a); print a[2]}' <<< "$line")
echo  "$CMD"

If you test this, it should output

user@host $ ./split_test.sh 
0   22  * * 1-5 
echo foo  bar   baz -o outfile.txt *

To explain

  • In both lines where the actual "splitting" is performed, I assign the variable on the left side the output from the command in the $( ... ) part (see e.g. the Advanced Bash Scripting Guide for examples), in which I feed the content of the variable $line, protected from globbing via the double parentheses (" ... "') to awk (so that, among others, white-space is preserved 'as is').
  • The awk call uses the internal match() function to look for specific patterns in the string, in our case one group ( ... ) at the start of the line containing any combination of digits, asterisks, whitespace and dashes, to unlimited number, followed by a group containing any printable characters, also to unlimited number. Since regular expressions are greedy, both parts will be matched by the longest possible sub-string, so group 1 really goes on until the first character which does not fit in there (i.e. the first alphanumeric character, or even ./) is found. The second group is then simply the remainder of the line and may contain all printable characters including numbers and special characters.
  • The idea behind the use of awk's match function is that if you have specified sub-groups by enclosing them in parentheses, you can instruct awk to fill the actual value of the patterns in an array, in this example a, such that a[1] contains the actual value of group 1 (the "scheduling" part), and a[2] the actual value of group 2 (the "command" part).
  • Although it is a little non-elegant, we apply this two times, having awk output the first part in the first call, and the second part in the second call, to assign the values to separate bash variables.

If you want to learn more about this, you can take a look at the GNU Awk User's Guide.

0

Crontab has five fields that describe the timing, the rest are your commands. See here.

So if you want to separate the time (fields 1-5) from the command then you just have to split on the 5th field separator.

sed "s/\s+/\n/5" <<<'1 2 3 * * echo hello * 7 8 9'
1 2 3 * *
echo hello * 7 8 9

Using a newline \n allows you to read the two strings into an array without a loop

readarray cron <<< $(sed 's/ /\n/5' <<<'1 2 3 * * echo hello * 7 8 9')

This does rely on \n not appearing as an escape sequence or inside a string in your crontab, although you can always concatenate elements of cron[1..n] to reassemble the command if it does.

0

You could use cut to separate values delimited by space like this:

#/bin/sh

var='0 22 * * 1-5 echo hello'

first=$(echo -n "$var" | cut -d' ' -f1)
second=$(echo -n "$var" | cut -d' ' -f2)
third=$(echo -n "$var" | cut -d' ' -f3)
fourth=$(echo -n "$var" | cut -d' ' -f4)
fifth=$(echo -n "$var" | cut -d' ' -f5)

restofvar=$(echo -n "$var" | cut -d' ' -f6,7)

E: As it was pointed in a comment, if there are multiple spaces in a string, you have to replace them with a single space:

var=$(echo "$var" | tr -s " ")
  • unfortunately, cut's delimiter does not handle multiple delimiters in a row. You have to hope there are only single spaces between the fields, but crontab entries allow sequences of whitespacde. – glenn jackman Nov 8 at 12:48
  • That's true, forgot about that. I edited my response :) – Shallan Nov 8 at 13:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.