CommandLine unstable
Builds command lines, the single strings that a shell or a Windows program
turns back into arguments, and argument lists from templates. It is
unrelated to effect/cli, which parses the arguments of your own program.
The rules depend on what parses the string, not on the platform that builds
it, so quote, join and display take an explicit format:
"posix"(the default) forsh,dash,bashandzsh. Quoted output is also read the same way byfish."powershell"for PowerShell 7.3 or later."windows"for the Microsoft C runtime rules that most Windows programs use to split their command line.
SSH sends one command line to the login shell of the remote account, so the
format must match that shell. The remote shell must be a POSIX shell or
PowerShell: Windows OpenSSH uses cmd.exe by default, which no format
supports, so set its DefaultShell to PowerShell. Docker and Kubernetes exec
take argument lists and need no command line at all.
Arguments can be Redacted strings. quote and join include their values,
because their output is meant to run, while display replaces them with
<redacted>.
Constructors
Builds an argument list from a template literal.
When to use
Use to write a command as it would look in a shell while keeping every interpolated value a literal argument.
Details
- The template text is split into words as a POSIX shell does: spaces, tabs and line breaks separate words, and quotes and backslash escapes are removed. A long command can span several lines.
- Text that would need a shell fails with
UnsupportedSyntax: operators (| & ; < > ( )), expansions ($NAME,${...},$(...), backticks, a leading~, braces such as{a,b}, and a leading=thatzshexpands), globs (* ? [), comments (#at the start of a word), and a variable assignment in the program position. Quote such characters to pass them literally. - No shell runs, so a program such as
ifortimenames an executable. - Each
${}value is inserted literally, without splitting or quote processing. Next to other text or inside quotes, it becomes part of the surrounding argument. - An array adds one argument per element and must stand alone as a whole argument.
- Numbers and booleans are converted with
String. - An argument that contains a
Redactedvalue becomesRedactedas a whole.
Gotchas
- An invalid JavaScript escape sequence in the text, such as
\uwithout hex digits, fails withInvalidEscape. UseString.rawor a${}value for backslashes. - Neither the text nor the values are checked for NUL characters. join and the child process spawners reject them where they would truncate an argument.
Signature
declare function template(strings: TemplateStringsArray, ...expressions: readonly Array<TemplateExpression>): Result<Array<Word>, CommandLineError>Example Building arguments with interpolated values
import { Redacted, Result } from "effect"import { CommandLine } from "effect/process"
const msg = "fix: handle $HOME"const files = ["a.ts", "b.ts"]
CommandLine.template`git commit -m ${msg} ${files}` // => Result.succeed(["git", "commit", "-m", msg, ...files])
const password = Redacted.make("hunter2")const words = Result.getOrThrow(CommandLine.template`psql "--password=${password}"`)
CommandLine.display(words) // => "psql <redacted>"Encoding
Renders a command for logs and error messages.
When to use
Use when you show a command to a person, for example in an error message, and secrets and control characters must not reach the output.
Details
- Quotes like join, so commands without control characters or secrets can be copied into a terminal of the given format.
- Replaces
Redactedarguments with<redacted>. - Writes control characters and Unicode line separators as escapes such as
\nor\u001b, so output cannot move the cursor or inject terminal sequences. - Never fails: input that join rejects is rendered anyway.
Gotchas
The output is for reading only. It is not a reliable command line: escapes
and <redacted> change what a shell would run.
See
- join for building a command line to run
Signature
declare function display(words: readonly Array<Word>, options?: { readonly format?: Format;}): stringExample Showing a command with a secret
import { Redacted } from "effect"import { CommandLine } from "effect/process"
const token = Redacted.make("hunter2")
CommandLine.display(["curl", "-H", token, "https://x.dev/a b"]) // => "curl -H <redacted> 'https://x.dev/a b'"Builds a command line that runs the given program and arguments.
When to use
Use when a transport accepts only one string, such as an SSH exec request or
a shell option, and the arguments must reach the program unchanged.
Details
The first word is the program. Each word is quoted as in quote and the words are separated by spaces.
"posix"also quotes a program name thatsh,bash,zshorfishreads as syntax, such asifortime."powershell"starts the line with the call operator&, because a quoted program name is otherwise read as a string value."windows"quotes the program name with the C runtime rules for program names, which do not support backslash escapes.- An empty list returns an empty string.
Gotchas
- The result contains the values of
Redactedarguments. Use display for logs. - A Windows program name that contains
"fails withInvalidArgument. - For
"powershell", an argument equal to--%fails withInvalidArgument: PowerShell drops it when it calls a native program, however it is quoted. "windows"output is the command line of a program, not acmd.exescript.cmd.exe, whichshell: trueuses on Windows, expands%NAME%and reads^ & | < >before the program sees its arguments, so a script that contains untrusted values has to be written by hand.
See
- display for rendering a command for logs and error messages
Signature
declare function join(words: readonly Array<Word>, options?: { readonly format?: Format;}): Result<string, CommandLineError>Example Joining arguments into command lines
import { Result } from "effect"import { CommandLine } from "effect/process"
const args = ["git", "commit", "-m", "fix: don't fail"]
CommandLine.join(args) // => Result.succeed("git commit -m 'fix: don'\\''t fail'")CommandLine.join(args, { format: "powershell" }) // => Result.succeed("& 'git' 'commit' '-m' 'fix: don''t fail'")CommandLine.join(args, { format: "windows" }) // => Result.succeed("git commit -m \"fix: don't fail\"")Quotes one argument so that a program reading the given format sees it as exactly one argument, with one PowerShell exception listed under Gotchas.
When to use
Use when you insert an argument into a command line that is otherwise
written by hand, for example a script passed to sh -c.
Details
"posix"leaves words made of letters, digits and_ @ + : , . / -unchanged and wraps everything else in single quotes. Single quotes and backslashes are written outside the quotes, sofishreads the result the same way."powershell"always uses single quotes, doubling'and the typographic single quotes that PowerShell also accepts as quotes."windows"quotes words that contain a space, a tab, a line break or a double quote, using the C runtime backslash rules. Use join for a complete command line, because the program name follows different rules.
Gotchas
- The result contains the value of a
Redactedargument. Use display for logs. - Control characters are kept as they are. That is safe for
sh -cand SSH commands, but not for text typed into an interactive shell. - PowerShell never passes an argument equal to
--%to a native program, however it is quoted. Use join for a whole native call, which fails withInvalidArgumentinstead.
See
- join for quoting a whole argument list
Signature
declare function quote(word: Word, options?: { readonly format?: Format;}): Result<string, CommandLineError>Example Quoting arguments for different shells
import { Result } from "effect"import { CommandLine } from "effect/process"
CommandLine.quote("it's") // => Result.succeed("'it'\\''s'")CommandLine.quote("it's", { format: "powershell" }) // => Result.succeed("'it''s'")CommandLine.quote("C:\\My Files\\", { format: "windows" }) // => Result.succeed("\"C:\\My Files\\\\\"")Errors
CommandLineError
unstableError returned when a command line or a template cannot be built.
Details
NulCharacter: an argument passed to quote or join contains a NUL character, which no command line can carry.InvalidArgument: the format cannot carry an argument: a Windows program name with a double quote, or the argument--%, which PowerShell drops when it calls a native program.InvalidEscape: template text ends with a backslash that escapes nothing, or contains an invalid JavaScript escape sequence.UnterminatedQuote: a quoted section of a template is not closed.UnsupportedSyntax: template text contains shell syntax that cannot work without a shell, such as operators, expansions, globs, comments or variable assignments, or an interpolated array is not a separate argument.
Messages never include the input, which may contain secrets.
Signature
declare class CommandLineError extends YieldableError<this> & { readonly _tag: "CommandLineError";} & Readonly<{ readonly message: string; readonly reason: "InvalidArgument" | "NulCharacter" | "InvalidEscape" | "UnterminatedQuote" | "UnsupportedSyntax";}> { constructor(args: { readonly message: string; readonly reason: "InvalidArgument" | "NulCharacter" | "InvalidEscape" | "UnterminatedQuote" | "UnsupportedSyntax"; });}Models
Command line syntax understood by the functions in this module.
Details
"posix": POSIX shells (sh,dash,bash,zsh). Quoted words are also safe infish."powershell": PowerShell 7.3 or later, which passes arguments to native programs unchanged ($PSNativeCommandArgumentPassingset toStandardorWindows)."windows": the Microsoft C runtime convention, also implemented byCommandLineToArgvW, that most Windows programs use. It is not the syntax ofcmd.exeor of batch files.
Signature
type Format = "posix" | "powershell" | "windows"Value accepted inside a ${} interpolation of template.
Details
An array adds one argument per element and must stand alone as a whole argument. An empty array adds no arguments.
Signature
type TemplateExpression = TemplateExpressionItem | ReadonlyArray<TemplateExpressionItem>Single value accepted inside a ${} interpolation of template.
Signature
type TemplateExpressionItem = string | number | boolean | Redacted.Redacted<string>One argument: a plain string or a Redacted string that must not appear in
logs.
Signature
type Word = string | Redacted.Redacted<string>