Parse complete crontab files with variable extraction, expression parsing, and error handling for system administration tasks and cron file management.
Parse crontab files asynchronously with Promise-based API.
/**
* Parse a crontab file asynchronously
* @param filePath - Path to the crontab file to parse
* @returns Promise resolving to parse results containing variables, expressions, and errors
* @throws Error if file cannot be read or doesn't exist
*/
static parseFile(filePath: string): Promise<CronFileParserResult>;
interface CronFileParserResult {
/** Environment variables extracted from the file */
variables: { [key: string]: string };
/** Successfully parsed cron expressions */
expressions: CronExpression[];
/** Parsing errors keyed by the problematic line */
errors: { [key: string]: unknown };
}Usage Examples:
import { CronFileParser } from "cron-parser";
// Parse a crontab file asynchronously
async function parseCrontabFile() {
try {
const result = await CronFileParser.parseFile('/etc/crontab');
console.log('Environment variables:');
Object.entries(result.variables).forEach(([key, value]) => {
console.log(` ${key}=${value}`);
});
console.log('\nCron expressions:');
result.expressions.forEach((expr, index) => {
console.log(` ${index + 1}: ${expr.toString()}`);
console.log(` Next run: ${expr.next().toString()}`);
});
if (Object.keys(result.errors).length > 0) {
console.log('\nParsing errors:');
Object.entries(result.errors).forEach(([line, error]) => {
console.log(` Line "${line}": ${error}`);
});
}
} catch (error) {
console.error('Failed to read crontab file:', error.message);
}
}
// Parse user-specific crontab
async function parseUserCrontab(username: string) {
const crontabPath = `/var/spool/cron/crontabs/${username}`;
try {
const result = await CronFileParser.parseFile(crontabPath);
console.log(`Crontab for user ${username}:`);
console.log(`Found ${result.expressions.length} cron jobs`);
console.log(`Found ${Object.keys(result.variables).length} environment variables`);
return result;
} catch (error) {
if (error.code === 'ENOENT') {
console.log(`No crontab found for user ${username}`);
} else {
console.error(`Error reading crontab for ${username}:`, error.message);
}
return null;
}
}
await parseCrontabFile();
const userResult = await parseUserCrontab('www-data');Parse crontab files synchronously for simpler control flow.
/**
* Parse a crontab file synchronously
* @param filePath - Path to the crontab file to parse
* @returns Parse results containing variables, expressions, and errors
* @throws Error if file cannot be read or doesn't exist
*/
static parseFileSync(filePath: string): CronFileParserResult;Usage Examples:
import { CronFileParser } from "cron-parser";
import * as fs from 'fs';
// Parse synchronously for simple cases
function loadSystemCrontab() {
try {
const result = CronFileParser.parseFileSync('/etc/crontab');
console.log('System crontab loaded successfully');
console.log(`Variables: ${Object.keys(result.variables).length}`);
console.log(`Expressions: ${result.expressions.length}`);
console.log(`Errors: ${Object.keys(result.errors).length}`);
return result;
} catch (error) {
console.error('Failed to load system crontab:', error.message);
return null;
}
}
// Compare multiple crontab files
function compareCrontabs(paths: string[]) {
const results: { [path: string]: CronFileParserResult | null } = {};
paths.forEach(path => {
try {
results[path] = CronFileParser.parseFileSync(path);
} catch (error) {
console.warn(`Skipping ${path}: ${error.message}`);
results[path] = null;
}
});
// Analyze results
Object.entries(results).forEach(([path, result]) => {
if (result) {
console.log(`${path}: ${result.expressions.length} jobs, ${Object.keys(result.errors).length} errors`);
}
});
return results;
}
const systemResult = loadSystemCrontab();
const comparison = compareCrontabs([
'/etc/crontab',
'/var/spool/cron/crontabs/root',
'/var/spool/cron/crontabs/www-data'
]);Understanding the crontab file format and parsing behavior.
// Crontab file format elements parsed by CronFileParser:
interface CrontabFileFormat {
/** Empty lines - ignored during parsing */
emptyLines: '';
/** Comment lines - ignored during parsing (start with #) */
comments: '# This is a comment';
/** Environment variable assignments */
variables: 'VARIABLE_NAME=value' | 'PATH=/usr/bin:/bin' | 'SHELL=/bin/bash';
/** Cron job entries - 5 fields for timing + command */
cronEntries: 'minute hour dayOfMonth month dayOfWeek command';
/** User-specific entries (system crontab) - 6 fields with user */
systemEntries: 'minute hour dayOfMonth month dayOfWeek user command';
}File Content Examples:
# Example /etc/crontab content that would be parsed:
# Environment variables
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# Empty lines and comments are ignored
# m h dom mon dow command
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
# Invalid entries would be captured in errors
99 25 32 13 8 root invalid-cron-entryUsage Examples:
// Create a test crontab content
const crontabContent = `
# Test crontab file
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
# Run backup every day at 2 AM
0 2 * * * /usr/local/bin/backup.sh
# Run reports every weekday at 9 AM
0 9 * * 1-5 /usr/local/bin/generate-reports.sh
# Invalid entry for testing
99 25 * * * invalid-command
# Run maintenance on first day of month
0 3 1 * * /usr/local/bin/monthly-maintenance.sh
`;
// Write test file and parse it
import * as fs from 'fs';
import * as path from 'path';
const testFile = path.join('/tmp', 'test-crontab');
fs.writeFileSync(testFile, crontabContent);
try {
const result = CronFileParser.parseFileSync(testFile);
console.log('Parsed variables:');
Object.entries(result.variables).forEach(([key, value]) => {
console.log(` ${key} = ${value}`);
});
/* Output:
SHELL = /bin/bash
PATH = /usr/local/bin:/usr/bin:/bin
MAILTO = admin@example.com
*/
console.log('\nParsed expressions:');
result.expressions.forEach((expr, i) => {
console.log(` ${i + 1}: ${expr.stringify()} - Next: ${expr.next().toString()}`);
});
/* Output:
1: 0 2 * * * - Next: [next 2 AM]
2: 0 9 * * 1-5 - Next: [next 9 AM weekday]
3: 0 3 1 * * - Next: [next 3 AM on 1st of month]
*/
console.log('\nParsing errors:');
Object.entries(result.errors).forEach(([line, error]) => {
console.log(` "${line}": ${error}`);
});
/* Output:
"99 25 * * * invalid-command": Constraint error, got value 99 expected range 0-59
*/
} finally {
// Clean up test file
fs.unlinkSync(testFile);
}Understanding and handling parsing errors for robust crontab processing.
// Error types that may appear in CronFileParserResult.errors
interface CronFileParsingErrors {
/** Invalid cron expression syntax */
SyntaxError: "Invalid cron expression";
/** Field values outside allowed ranges */
ConstraintError: "Constraint error, got value [value] expected range [min]-[max]";
/** Invalid characters in cron fields */
CharacterError: "Invalid characters, got value: [value]";
/** Strict mode validation failures */
StrictModeError: "Cannot use both dayOfMonth and dayOfWeek together in strict mode!";
/** General parsing failures */
GeneralError: Error; // Any other parsing error
}Usage Examples:
// Robust crontab parsing with comprehensive error handling
async function robustCrontabParse(filePath: string, options = {}) {
try {
const result = await CronFileParser.parseFile(filePath);
const stats = {
totalLines: 0,
variableLines: Object.keys(result.variables).length,
expressionLines: result.expressions.length,
errorLines: Object.keys(result.errors).length,
commentLines: 0 // Would need to count manually from file content
};
console.log(`Parsing statistics for ${filePath}:`);
console.log(` Variables: ${stats.variableLines}`);
console.log(` Valid expressions: ${stats.expressionLines}`);
console.log(` Errors: ${stats.errorLines}`);
// Categorize errors
const errorTypes = {
constraint: 0,
syntax: 0,
character: 0,
other: 0
};
Object.values(result.errors).forEach(error => {
const errorMsg = error.toString().toLowerCase();
if (errorMsg.includes('constraint')) {
errorTypes.constraint++;
} else if (errorMsg.includes('invalid cron')) {
errorTypes.syntax++;
} else if (errorMsg.includes('invalid characters')) {
errorTypes.character++;
} else {
errorTypes.other++;
}
});
if (stats.errorLines > 0) {
console.log(' Error breakdown:');
Object.entries(errorTypes).forEach(([type, count]) => {
if (count > 0) {
console.log(` ${type}: ${count}`);
}
});
}
// Validate parsed expressions
const validatedExpressions = result.expressions.filter(expr => {
try {
// Test if expression can generate next date
expr.hasNext();
return true;
} catch (error) {
console.warn(`Expression validation failed: ${expr.toString()} - ${error.message}`);
return false;
}
});
console.log(` Validated expressions: ${validatedExpressions.length}/${result.expressions.length}`);
return {
...result,
stats,
errorTypes,
validatedExpressions
};
} catch (fileError) {
console.error(`File access error for ${filePath}:`, fileError.message);
return null;
}
}
// Batch process multiple crontab files with error recovery
async function batchParseCrontabs(filePaths: string[]) {
const results = await Promise.allSettled(
filePaths.map(path => robustCrontabParse(path))
);
const summary = {
successful: 0,
failed: 0,
totalExpressions: 0,
totalErrors: 0
};
results.forEach((result, index) => {
const filePath = filePaths[index];
if (result.status === 'fulfilled' && result.value) {
summary.successful++;
summary.totalExpressions += result.value.expressions.length;
summary.totalErrors += Object.keys(result.value.errors).length;
} else {
summary.failed++;
console.error(`Failed to parse ${filePath}:`,
result.status === 'rejected' ? result.reason : 'Unknown error');
}
});
console.log('\nBatch parsing summary:');
console.log(` Successful files: ${summary.successful}`);
console.log(` Failed files: ${summary.failed}`);
console.log(` Total expressions: ${summary.totalExpressions}`);
console.log(` Total errors: ${summary.totalErrors}`);
return summary;
}
// Example usage
const crontabFiles = [
'/etc/crontab',
'/var/spool/cron/crontabs/root',
'/var/spool/cron/crontabs/www-data',
'/etc/cron.d/custom-jobs'
];
const batchResult = await batchParseCrontabs(crontabFiles);Common patterns for working with parsed crontab data.
// Common utility patterns for working with CronFileParserResult
interface CrontabAnalysis {
/** Find all jobs running at specific times */
findJobsByTime(hour: number, minute?: number): CronExpression[];
/** Find all jobs running on specific days */
findJobsByDay(dayOfWeek: number): CronExpression[];
/** Get all unique schedules */
getUniqueSchedules(): string[];
/** Find potentially conflicting jobs */
findConflicts(): { time: string, expressions: CronExpression[] }[];
/** Validate all expressions against current time */
validateExpressions(): { valid: CronExpression[], invalid: { expr: CronExpression, error: string }[] };
}Usage Examples:
// Advanced crontab analysis utilities
class CrontabAnalyzer {
constructor(private result: CronFileParserResult) {}
// Find all jobs that run at a specific time
findJobsByTime(hour: number, minute: number = 0): CronExpression[] {
return this.result.expressions.filter(expr => {
const testDate = new Date();
testDate.setHours(hour, minute, 0, 0);
try {
return expr.includesDate(testDate);
} catch {
return false;
}
});
}
// Find jobs running on specific days
findJobsByDay(dayOfWeek: number): CronExpression[] {
return this.result.expressions.filter(expr => {
return expr.fields.dayOfWeek.values.includes(dayOfWeek);
});
}
// Get schedule frequency analysis
getScheduleAnalysis() {
const frequencies = {
minutely: 0,
hourly: 0,
daily: 0,
weekly: 0,
monthly: 0,
yearly: 0,
custom: 0
};
this.result.expressions.forEach(expr => {
const schedule = expr.stringify();
if (schedule.startsWith('*')) frequencies.minutely++;
else if (schedule.includes('* * * *')) frequencies.hourly++;
else if (schedule.includes('* * *') && !schedule.includes('* * * *')) frequencies.daily++;
else if (schedule.includes('* * 0') || schedule.includes('* * 7')) frequencies.weekly++;
else if (schedule.includes('1 * *')) frequencies.monthly++;
else if (schedule.includes('1 1 *')) frequencies.yearly++;
else frequencies.custom++;
});
return frequencies;
}
// Find next execution times for all jobs
getUpcomingExecutions(limit: number = 5): { expression: string, nextRun: Date }[] {
const upcoming: { expression: string, nextRun: Date, cronExpr: CronExpression }[] = [];
this.result.expressions.forEach(expr => {
try {
const next = expr.next();
upcoming.push({
expression: expr.stringify(),
nextRun: new Date(next.getTime()),
cronExpr: expr
});
expr.reset(); // Reset to avoid affecting original
} catch (error) {
// Skip expressions that can't generate next execution
}
});
// Sort by next execution time and return top N
return upcoming
.sort((a, b) => a.nextRun.getTime() - b.nextRun.getTime())
.slice(0, limit)
.map(({ expression, nextRun }) => ({ expression, nextRun }));
}
}
// Example usage
async function analyzeCrontab(filePath: string) {
try {
const result = await CronFileParser.parseFile(filePath);
const analyzer = new CrontabAnalyzer(result);
console.log(`\nAnalyzing ${filePath}:`);
// Schedule frequency analysis
const frequencies = analyzer.getScheduleAnalysis();
console.log('Schedule frequencies:', frequencies);
// Find jobs running at specific times
const jobsAt2AM = analyzer.findJobsByTime(2, 0);
console.log(`Jobs running at 2:00 AM: ${jobsAt2AM.length}`);
const weekendJobs = analyzer.findJobsByDay(0).concat(analyzer.findJobsByDay(6));
console.log(`Weekend jobs: ${weekendJobs.length}`);
// Upcoming executions
const upcoming = analyzer.getUpcomingExecutions(10);
console.log('\nNext 10 executions:');
upcoming.forEach(({ expression, nextRun }, i) => {
console.log(` ${i + 1}. ${expression} - ${nextRun.toLocaleString()}`);
});
} catch (error) {
console.error(`Analysis failed for ${filePath}:`, error.message);
}
}
await analyzeCrontab('/etc/crontab');