Technote

Development workflow Practical

Series WP-CLI automation recipes Part 4 of 8

Running scripts with eval-file, and the argument trap

It runs your own PHP file inside a fully loaded WordPress. Flags starting with -- never reach your script, though, because WP-CLI consumes them first.

Some jobs do not decompose into commands: branching on a condition, looping over hundreds of records, tying several steps into one operation. That is what wp eval-file is for.

eval and eval-file

One line goes through wp eval; a file goes through wp eval-file.

wp eval 'echo get_option( "home" );'
wp eval-file bin/report.php

Both run inside a fully loaded WordPress. Plugins are active, hooks are attached, and get_posts() and wp_insert_post() behave exactly as they do on the front end. Not having to write your own bootstrap is the whole value of the command.

Put a guard at the top of the script file.

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

Now if the file ever ends up inside the document root, a web request does nothing with it. For any script that writes data, this is not optional.

The trap: arguments are positional

Everybody hits this once. Anything beginning with -- is interpreted as a flag for WP-CLI itself. It never reaches your script, and an unrecognised one gets the whole command rejected.

# does not work — --dry is consumed by eval-file
wp eval-file bin/seed.php --dry

# works — pass it positionally
wp eval-file bin/seed.php dry s25

Inside the script they arrive in $args.

$args = (array) ( $args ?? [] );
$dry  = in_array( 'dry', $args, true );

The restriction is more useful than it first appears, because it keeps a script’s interface down to a couple of short words. Once you want more than three options, the job has outgrown a script and it is time to promote it to a proper command — the subject of part six.

Move tools as the job grows — the argument convention marks the boundary

WordPress in the shell is not WordPress on the web

Same WordPress, different context. Three differences cause most of the surprises.

What differs in the CLI context — the top row even changes how content saves

User 0 reaches furthest. With nobody logged in every current_user_can() is false, and the absence of unfiltered_html in particular means kses filters your content on save. The next part shows exactly what that looks like when it happens.

Hook timing differs too. eval-file executes after WordPress has finished loading, so early hooks such as init are long gone. People add add_action( 'init', … ) inside a script and then hunt for why it never fires; it is late rather than wrong, and the answer is simply to call the code directly.

The script that dies silently on an ABSPATH guard

Most WordPress files open with this.

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

require such a file from a plain PHP script that never bootstrapped WordPress and the line executes as written, ending the run with zero bytes of output and exit code 0. No error, no warning — so you start by suspecting your own logic, and it takes a long while to find.

There are two clean responses. If you need WordPress, run through eval-file. If you do not, write a self-contained script that includes no WordPress files at all. The second is especially valuable in CI, where it finishes in seconds without a database or an install — we come back to it in part eight.

Examples of the kind of work worth scripting live in the development workflow archive, and the diagnostic plugins we built the same way are on the free tools page.

Next part

Once you write scripts, you will soon run the same script twice. Next up: idempotent seeds — making a second run create nothing new — and the several quiet traps hiding in that.

More on this topic

All technotes

Development workflow Advanced

Running WP-CLI in CI: what the checks actually look like

Checks that rely on somebody remembering get skipped in a busy week. Stack the cheap ones first, run the rest against a disposable WordPress, and the remembering stops…

Developers 8 min read

₩270,000 · Join the program