shift-org / shift-org/shift-docs
Passing arguments to `./shift` subcommands is broken by unquoted `$@`
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 30
- Forks
- 25
- Avg merge
- 9m
- Merged PRs (30d)
- 1
Description
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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in ./shift, tracing the dispatcher, sub_mysql, and sub_compose call chain named in the issue. Reproduce the mysql command, then inspect each affected forwarding subcommand: mysql, mysql-pipe, mysqldump, compose, attach, and logs. Done when arguments remain intact through the chain, the SQL command returns variables instead of usage output, and glob characters are not expanded.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, docker-compose
- Domain
- cli, devops
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100