fix: quote empty arguments and escape apostrophes when building a shell command line - #2711
Draft
acinader wants to merge 3 commits into
Draft
fix: quote empty arguments and escape apostrophes when building a shell command line#2711acinader wants to merge 3 commits into
acinader wants to merge 3 commits into
Conversation
quoteForCommandLine returns a string unchanged when it contains no quote, whitespace or shell characters. An empty string contains none, so it comes back unquoted, and unwords drops it: unwords (map quoteForCommandLine ["a", "", "b"]) == "a b" Every caller that builds a shell command line loses empty arguments as a result. Run.hs does, for addon commands: $ cat cmds.txt zzzz a "" b before hledger run -f /dev/null cmds.txt argv: [-f] [/dev/null] [a] [b] after argv: [-f] [/dev/null] [a] [] [b] Quote the empty string. New case 31 in run.test covers the addon path. AI usage: Claude Opus 5, ~9k output tokens
repl dispatches addons the same way run does, so it lost empty arguments for the same reason. Case 32 covers it. AI usage: Claude Opus 5, ~3k output tokens
Contributor
Author
|
find a way to test this on windows |
quoteForCommandLine wraps an argument in single quotes when it contains a quote, whitespace or shell character, and escapeSingleQuotes escaped an embedded apostrophe as \'. A backslash is not an escape inside single quotes in POSIX sh, so the quote closes at the apostrophe and the rest of the argument is no longer carried as one argument: argument it's before sent to sh as 'it\'s' -> unterminated quote, sh rejects the line after sent to sh as 'it'\''s' -> addon receives [it's] Use the POSIX idiom instead: close the quote, emit the apostrophe, reopen it. This affects the callers that still build a shell command line rather than passing an argv list: Run.hs for addons under run and repl, and the ! shell alias path in Cli.hs. An apostrophe in a payee or description is common enough to hit in normal use, eg `hledger ui "Trader Joe's"`. New case 33 in run.test covers the run path; a doctest covers quoteForCommandLine directly. AI usage: Claude Opus 5, ~3k output tokens
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
quoteForCommandLinehas two defects, both of which lose or mangle an argument onits way to a shell command line.
Empty arguments vanish. It returns a string unchanged when it contains no
quote, whitespace or shell character. An empty string contains none, so it comes
back unquoted and
unwordsdrops it:Apostrophes break the quoting. When an argument does need quoting it is
wrapped in single quotes, and
escapeSingleQuotesescaped an embedded apostropheas
\'. A backslash is not an escape inside single quotes in POSIX sh, so thequote closes at the apostrophe and the line no longer parses:
Both are fixed in
quoteForCommandLineitself: an empty string becomes'', andan apostrophe uses the POSIX idiom of closing the quote, emitting it, and
reopening.
What this affects
Every caller that builds a shell command line rather than passing an argv list:
Run.hsfor addons underrunandrepl, and the!shell alias path inCli.hs.An apostrophe in a payee or description is common enough to hit in ordinary use —
hledger ui "Trader Joe's"fails on main.Windows
This does not close the Windows gap.
quoteForCommandLinehas no platform branch,and cmd.exe does not recognise single quotes as a quoting character —
shellQuoteIfNeeded's docstring says exactly that, and uses double-quote escapingthere instead. So on Windows
''would arrive as two literal characters ratherthan an empty argument. Closing that would mean making
quoteForCommandLineplatform-aware.
I have a CI probe showing empty arguments survive on Windows when quoted as
"",but that is a different form from what this PR emits, so I am not claiming the
Windows half is fixed. I'll confirm the actual behaviour separately.
Tests
run.test31 covers an empty argument throughrun, 32 throughrepl, and 33 anargument containing an apostrophe. A doctest covers
quoteForCommandLinedirectly.All fail on main and pass with the fix.
Full functional suite 1735/1735, doctests 297/297, builds warning-free under
-Werror.Provenance
Found while testing #2699, but independent of it: the defect is in
hledger-liband predates that PR. It stands on its own against main and can land in any order.
The apostrophe half was also reached independently in the review session linked
from #2699 (finding 5), which suggested fixing both in
quoteForCommandLinetogether rather than special-casing them at the call sites — which is what this
now does.
Ready for merge but marked draft to comply with pr policy.
AI usage: Claude Opus 5, ~15k output tokens