Skip to content

Passing arguments to ./shift subcommands is broken by unquoted $@ #1081

Description

@dduugg

Any ./shift subcommand that forwards arguments mangles them, because $@ is passed unquoted at every level of the call chain. The most visible casualty is ./shift mysql -e "<sql>", which never runs the SQL and instead prints mysql's usage text.

Reproducing

$ ./shift mysql -e "SHOW VARIABLES LIKE 'character_set%';"

Expected: the variables. Actual: mysql's help/usage output.

Cause

The SQL string is split on whitespace before mysql ever sees it. shift is a bash script (#!/usr/bin/env bash), and unquoted $@ in bash re-splits each positional parameter on IFS and then glob-expands the results. The relevant chain, all unquoted:

# dispatcher, bottom of the file
sub_${SUB_CMD} $@

sub_mysql() { #                   - Open a mysql prompt with the db selected
    cd "${ROOT}"
    sub_compose exec db mysql -u ${MYSQL_USER} -h db -P 3306 -p"${MYSQL_PASSWORD}" ${MYSQL_DATABASE} $@
}

sub_compose() { # <cmd...>        - Run a compose with associated files
    docker compose $@
}

So -e "SHOW VARIABLES LIKE 'character_set%';" arrives at mysql as five separate arguments:

[-e] [SHOW] [VARIABLES] [LIKE] ['x%';]

mysql receives -e SHOW, then treats VARIABLES, LIKE, and the rest as stray arguments, and bails out to its usage text.

Glob expansion makes it worse. Because the split words are also subject to pathname expansion, an asterisk in the SQL expands against the current directory:

$ ./shift mysql -e "SELECT * FROM calevent LIMIT 1"

delivers this to mysql:

[-e] [SELECT] [app] [backend] [bin] [cal] [CLAUDE.md] [db-local-empty.sql]
[docker-compose.yml] [docs] [LICENSE.md] [netlify.toml] ... [services]

Note that quoting $@ in sub_mysql alone is not sufficient, since the arguments are re-split again inside sub_compose, and once more by the dispatcher.

Affected subcommands

Every one that forwards $@: mysql, mysql-pipe, mysqldump, compose, attach, logs, and the dispatcher itself. mysql-pipe happens to work in practice only because SQL reaches it on stdin rather than as an argument.

Workaround

Pipe the SQL instead of passing it as an argument:

printf "SHOW VARIABLES LIKE 'character_set%%';\n" | ./shift mysql-pipe

Suggested fix

Quote "$@" at each level (dispatcher, sub_compose, and each sub_* that forwards arguments). Worth a pass over the other unquoted expansions in the same lines while in there.

This is easy to trip over and hard to diagnose, since mysql's usage output does not look like a quoting error. It also bit the verification steps written into #1077.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions