find walks a tree and selects files by test (-name '*.php', -type f, -mtime +30, -size +40k), then acts with -print, -delete or -exec cmd {} +; -path ./vendor -prune -o skips a subtree. xargs turns its input into arguments, and -print0 with xargs -0 separates names with NUL bytes so a space cannot split one in two. -n 50 passes 50 files per run and -P 4 runs four at once; since PHP 8.3, one php -l call lints several files. jq 133,477 is sed and awk for JSON (composer.json, package.json, every REST API): .key selects a field, .[] iterates an array, | chains filters, select(cond) keeps matches and -r prints raw strings. Ubuntu 26.04 225 ships jq 1.8.1 (sudo apt install jq, github.com/jqlang/jq (https://github.com/jqlang/jq 35,709 )); upstream released 1.8.2 with security fixes on 20 June 2026.
composer require --quiet monolog/monolog guzzlehttp/guzzle # a small real project
mkdir src && printf '<?php\necho "ok";\n' > src/Good.php && printf '<?php\nfn( {\n' > src/Bad.php
find vendor -name '*.php' -print0 | xargs -0 -n 50 -P 4 php -l | grep -vc '^No syntax errors'
find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 1 php -l 2>&1 | cut -c1-72
find . -path ./vendor -prune -o -name '*.php' -exec php -l {} + 2>&1 | cut -c1-72
jq .require composer.json
jq -r '.packages[] | [.name, .version, .license[0]] | @tsv' composer.lock | column -t | head -30
PHP Parse error: syntax error, unexpected token "{", expecting variable
Errors parsing ./src/Bad.php
xargs: php: exited with status 255; aborting
PHP Parse error: syntax error, unexpected token "{", expecting variable
Errors parsing ./src/Bad.php
No syntax errors detected in ./src/Good.php
{
"monolog/monolog": "^3.12",
"guzzlehttp/guzzle": "^8.2"
}
guzzlehttp/guzzle 8.2.0 MIT
guzzlehttp/promises 3.0.2 MIT
guzzlehttp/psr7 3.1.0 MITThe vendor tree lints clean. The second run is a real trap: php -l exits with status 255 on a parse error, GNU xargs stops at once on 255, and Good.php is never checked. find -exec ... {} + batches too but carries on, so prefer it in CI. jq never edits in place: write to a temporary file, then mv it over the original.