Documentation
¶
Overview ¶
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting. It wraps mvdan.cc/sh/v3/syntax to provide a simple API for extracting command names from shell scripts, similar to how hadolint uses ShellCheck.
Index ¶
- Variables
- func Basename(p string) string
- func CommandNames(script string) []string
- func CommandNamesWithVariant(script string, variant Variant) []string
- func ContainsCommand(script, command string) bool
- func ContainsCommandWithVariant(script, command string, variant Variant) bool
- func CountChainedCommands(script string, variant Variant) int
- func DownloadOutputFile(cmd *CommandInfo) string
- func DownloadURL(cmd *CommandInfo) string
- func DropQuotes(s string) string
- func ExtractChainSeparators(script string, variant Variant, commandCount int) []string
- func ExtractChainedCommands(script string, variant Variant) []string
- func ExtractCommandsBetweenCds(remaining string, variant Variant) string
- func FormatOctalMode(mode uint16) string
- func FormatStatement(stmt *syntax.Stmt, variant Variant) string
- func HasCdAtStart(script string, variant Variant) bool
- func HasExitCommand(script string, variant Variant) bool
- func HasPipes(script string, variant Variant) bool
- func HasStandaloneCd(script string, variant Variant) bool
- func IsArchiveFilename(name string) bool
- func IsArchiveURL(s string) bool
- func IsHeredocCandidate(script string, variant Variant, minCommands int) bool
- func IsPureFileCreation(script string, variant Variant) bool
- func IsSimpleScript(script string, variant Variant) bool
- func IsTarExtract(cmd *CommandInfo) bool
- func IsURL(s string) bool
- func IterateWrapperArgs(args []*syntax.Word, wrapperName string, callback func(WrapperArg) bool)
- func ParseOctalMode(s string) uint16
- func SetsErrorFlag(cmd string, variant Variant) bool
- func SplitSimpleCommand(cmd string, variant Variant) ([]string, bool)
- func TarDestination(cmd *CommandInfo) string
- type CdCommand
- type ChainPosition
- type ChmodInfo
- type CommandInfo
- type CommandMatcher
- type CommandOccurrence
- type FileCreationInfo
- type PackageInstallInfo
- type PackageManager
- type Variant
- type WrapperArg
Constants ¶
This section is empty.
Variables ¶
var ArchiveExtensions = []string{
".tar.lzma",
".tar.bz2",
".tar.gz",
".tar.xz",
".tar.zst",
".tar.lz",
".tar.Z",
".lzma",
".tbz2",
".tzst",
".tar",
".tbz",
".tb2",
".tgz",
".tlz",
".tpz",
".txz",
".bz2",
".tZ",
".gz",
".lz",
".xz",
".Z",
}
ArchiveExtensions is the unified superset of archive file extensions recognized by both DL3010 (hadolint) and prefer-add-unpack (tally). Sorted longest-first so suffix matching is greedy (e.g. ".tar.gz" is checked before ".gz").
var DownloadCommands = []string{"curl", "wget"}
DownloadCommands lists commands that download remote files.
var ExtractionCommands = []string{
"bunzip2",
"gzcat",
"gunzip",
"uncompress",
"unlzma",
"unxz",
"unzip",
"zcat",
"zgz",
}
ExtractionCommands lists commands that extract archive files (excluding tar, which needs separate flag checking via IsTarExtract).
Functions ¶
func Basename ¶
Basename extracts the filename from a path, stripping quotes and handling both Unix and Windows separators.
func CommandNames ¶
CommandNames extracts all command names from a shell script. Uses VariantBash by default. Use CommandNamesWithVariant for other shells.
func CommandNamesWithVariant ¶
CommandNamesWithVariant extracts all command names from a shell script using the specified shell variant for parsing.
It parses the script and walks the AST to find all CallExpr nodes, returning the first word of each (the command name). It also handles command wrappers (env, nice, xargs, etc.) and shell wrappers (sh -c, bash -c).
This matches hadolint's behavior using ShellCheck.findCommandNames.
func ContainsCommand ¶
ContainsCommand checks if a shell script contains a specific command. Uses VariantBash by default.
func ContainsCommandWithVariant ¶
ContainsCommandWithVariant checks if a shell script contains a specific command using the specified shell variant for parsing.
func CountChainedCommands ¶
CountChainedCommands counts the number of commands in && chains within a shell script. Pipelines (|) count as a single logical command. Top-level statements separated by semicolons or newlines are counted individually.
func DownloadOutputFile ¶
func DownloadOutputFile(cmd *CommandInfo) string
DownloadOutputFile extracts the output filename from a curl or wget CommandInfo. For curl: -o <file>, -o<file>, --output <file>, --output=<file> For wget: -O <file>, -O<file>, --output-document <file>, --output-document=<file> Returns "" if no output file is specified or if output is stdout ("-").
func DownloadURL ¶
func DownloadURL(cmd *CommandInfo) string
DownloadURL extracts the first URL argument (http/https/ftp) from a download CommandInfo. Returns "" if no URL is found.
func DropQuotes ¶
DropQuotes removes surrounding single or double quotes from a string.
func ExtractChainSeparators ¶ added in v0.10.0
ExtractChainSeparators returns the raw separator text between commands in an && chain (for example " && " or " && \\\n "). The result length is commandCount-1 on success; otherwise nil.
func ExtractChainedCommands ¶
ExtractChainedCommands extracts individual command strings from && chains. Each command is formatted cleanly using the shell printer. Returns nil if parsing fails or for non-POSIX shells.
func ExtractCommandsBetweenCds ¶
ExtractCommandsBetweenCds parses the remaining commands after a cd and extracts commands that come before the next cd. This properly handles quoted paths. For "make && cd /tmp && build", if we're looking for commands before "cd /tmp", this returns "make".
func FormatOctalMode ¶
FormatOctalMode formats a chmod mode as a 4-digit octal string. E.g., 0o755 -> "0755", 0o644 -> "0644" Returns empty string for 0 (no mode).
func FormatStatement ¶
FormatStatement formats a single statement using syntax.Printer. Returns a clean, single-line representation of the command.
func HasCdAtStart ¶
HasCdAtStart returns true if the script has cd at the beginning of a command chain.
func HasExitCommand ¶
HasExitCommand checks if a script contains exit commands that would change control flow if merged with other commands.
func HasPipes ¶
HasPipes checks if a shell script contains any pipe operators (| or |&). Returns false for non-POSIX shells or unparseable scripts.
func HasStandaloneCd ¶
HasStandaloneCd returns true if the script contains a standalone cd command (one that isn't chained with other commands).
func IsArchiveFilename ¶
IsArchiveFilename checks if a filename has a recognized archive extension. Extensions are case-sensitive (e.g. .Z and .tZ use uppercase Z for Unix compress format).
func IsArchiveURL ¶
IsArchiveURL checks if a URL string points to an archive file. Strips query/fragment before checking extension. Requires http/https/ftp scheme.
func IsHeredocCandidate ¶
IsHeredocCandidate checks if a shell script would be a good candidate for heredoc conversion by the prefer-run-heredoc rule. This is used by other rules (like DL3003) to avoid generating fixes that would interfere with heredoc conversion.
A script is a heredoc candidate if:
- It uses a POSIX shell
- It has at least minCommands commands (from && chains or separate statements)
- It's a simple script (no complex control flow like if/for/while)
- It doesn't contain exit commands
This function parses the script once and reuses the AST for all checks.
func IsPureFileCreation ¶
IsPureFileCreation checks if a shell script is PURELY for creating files. Returns true only if every command in the script is for file creation (echo/cat/printf > file) or chmod on the created file. Returns false if there are any other commands mixed in. This is used by prefer-run-heredoc to yield to prefer-copy-heredoc.
func IsSimpleScript ¶
IsSimpleScript checks if a script contains only simple commands that can be safely merged into a heredoc. Returns false for scripts with compound commands (if, for, while, case), control flow (exit, return), functions, or subshells.
func IsTarExtract ¶
func IsTarExtract(cmd *CommandInfo) bool
IsTarExtract checks if a tar CommandInfo has extraction flags (-x, --extract, --get).
func IterateWrapperArgs ¶
func IterateWrapperArgs(args []*syntax.Word, wrapperName string, callback func(WrapperArg) bool)
IterateWrapperArgs iterates through wrapper command arguments, skipping flags and their values, and calls the callback for each potential command argument found. This handles the common pattern of finding commands within sudo, env, etc.
The callback should return true to break iteration, false to continue looking for nested wrappers.
func ParseOctalMode ¶
ParseOctalMode parses an octal mode string (e.g., "755", "0755") to uint16. Returns 0 for invalid input.
func SetsErrorFlag ¶
SetsErrorFlag checks if a command is a "set" builtin that enables the -e flag. Uses shell AST to properly detect any flag combination containing 'e' (e.g., "set -e", "set -ex", "set -euo pipefail").
func SplitSimpleCommand ¶
SplitSimpleCommand parses a shell command string and returns its argv words.
This is intentionally conservative: it only succeeds for a single simple command without redirections, pipelines, boolean operators, variable expansions, command substitutions, or other shell-specific constructs.
This is useful for suggesting "exec form" JSON arrays for Dockerfile instructions like CMD/ENTRYPOINT when the shell form is trivially tokenizable.
func TarDestination ¶
func TarDestination(cmd *CommandInfo) string
TarDestination extracts the target directory from a tar CommandInfo. Checks -C <dir>, --directory=<dir>, --directory <dir>. Returns "" if none found.
Types ¶
type CdCommand ¶
type CdCommand struct {
// TargetDir is the directory argument passed to cd.
TargetDir string
// IsStandalone is true if cd is the only command (not chained with && or ;).
IsStandalone bool
// IsAtStart is true if cd is at the beginning of a command chain.
// e.g., "cd /foo && make" has cd at start, "make && cd /foo" does not.
IsAtStart bool
// PrecedingCommands contains the commands before cd if it's not at the start.
// e.g., for "mkdir /tmp && cd /tmp && make", this would be "mkdir /tmp".
PrecedingCommands string
// RemainingCommands contains the commands after "cd /foo &&" if IsAtStart is true.
// Empty if IsStandalone is true or cd is not at start.
RemainingCommands string
// StartCol is the 0-based column where cd starts.
StartCol int
// Line is the 0-based line number.
Line int
}
CdCommand represents a cd command found in a shell script.
func FindCdCommands ¶
FindCdCommands finds all cd commands in a shell script and analyzes their context.
type ChainPosition ¶
type ChainPosition struct {
// IsStandalone is true if this is the only command (not chained).
IsStandalone bool
// HasOtherStatements is true when the script contains multiple top-level
// statements separated by semicolons or newlines. In this case,
// PrecedingCommands and RemainingCommands only cover the chain within
// the matched statement and do NOT include commands from other statements.
// Callers building replacement text for the entire script must not use
// this position alone, as it would silently drop sibling statements.
HasOtherStatements bool
// PrecedingCommands contains the commands before this one in the chain.
// Empty when the command is at the start or standalone.
PrecedingCommands string
// RemainingCommands contains the commands after this one in the chain.
// Empty when the command is at the end or standalone.
RemainingCommands string
}
ChainPosition describes a command's position within a && chain.
func FindCommandInChain ¶
func FindCommandInChain(script string, variant Variant, match CommandMatcher) *ChainPosition
FindCommandInChain locates the first command matching the predicate in a shell script and returns its chain context (preceding/remaining commands). Returns nil if no matching command is found or the script fails to parse.
type ChmodInfo ¶
type ChmodInfo struct {
// Mode is the octal mode (e.g., 0o755, 0o644, 0o4755).
Mode uint16
// Target is the file path being chmod'd.
Target string
}
ChmodInfo describes a standalone chmod command.
func DetectStandaloneChmod ¶
DetectStandaloneChmod checks if a shell script is a standalone chmod command. Returns nil if it's not a pure chmod or if the chmod cannot be converted (e.g., symbolic mode, recursive chmod, multiple commands).
type CommandInfo ¶
type CommandInfo struct {
// Name is the base command name (e.g., "apt-get", "yum").
Name string
// Subcommand is the first non-flag argument (e.g., "install" in "apt-get install").
Subcommand string
// Args contains all arguments including flags.
Args []string
// Position information for the command name.
Line int // 0-based line within the script
StartCol int // 0-based column where command starts
EndCol int // 0-based column where command name ends
// Position information for the subcommand (if present).
// These are only set when Subcommand is non-empty.
SubcommandLine int // 0-based line within the script
SubcommandStartCol int // 0-based column where subcommand starts
SubcommandEndCol int // 0-based column where subcommand ends
}
CommandInfo represents a parsed command with its arguments and flags.
func FindCommands ¶
func FindCommands(script string, variant Variant, names ...string) []CommandInfo
FindCommands extracts all commands matching the given name(s) from a shell script. It returns detailed CommandInfo for each matching command.
func (*CommandInfo) CountFlag ¶
func (c *CommandInfo) CountFlag(flag string) int
CountFlag counts how many times a flag appears in the command. Useful for checking flags like -q -q (equivalent to -qq).
func (*CommandInfo) GetArgValue ¶
func (c *CommandInfo) GetArgValue(flag string) string
GetArgValue returns the value following a flag (e.g., "-q=2" returns "2"). Returns empty string if not found or no value.
func (*CommandInfo) HasAnyArg ¶
func (c *CommandInfo) HasAnyArg(args ...string) bool
HasAnyArg checks if any of the specified arguments are present as the subcommand.
func (*CommandInfo) HasAnyFlag ¶
func (c *CommandInfo) HasAnyFlag(flags ...string) bool
HasAnyFlag checks if the command has any of the specified flags.
func (*CommandInfo) HasFlag ¶
func (c *CommandInfo) HasFlag(flag string) bool
HasFlag checks if the command has a specific flag. Handles both short flags (-y) and long flags (--yes). For short flags, also checks combined flags (e.g., -yq contains -y).
type CommandMatcher ¶
CommandMatcher is a predicate that decides whether a shell call expression is the command to locate. name is the base command name (path stripped), args are all arguments (flags and positional).
type CommandOccurrence ¶
type CommandOccurrence struct {
// Name is the command name (e.g., "apt", "sudo").
Name string
// Subcommand is the first argument if it looks like a subcommand (e.g., "install" in "apt install").
// Empty if the first argument looks like a flag or there are no arguments.
Subcommand string
// StartCol is the 0-based column offset where the command starts.
StartCol int
// EndCol is the 0-based column offset where the command name ends (exclusive).
EndCol int
// Line is the 0-based line number within the script where the command appears.
Line int
}
CommandOccurrence represents a command with its exact position in the script.
func FindAllCommandOccurrences ¶
func FindAllCommandOccurrences(script, command string, variant Variant) []CommandOccurrence
FindAllCommandOccurrences finds all occurrences of a specific command.
func FindCommandOccurrence ¶
func FindCommandOccurrence(script, command string, variant Variant) *CommandOccurrence
FindCommandOccurrence finds the first occurrence of a specific command. Returns nil if the command is not found.
func FindCommandOccurrences ¶
func FindCommandOccurrences(script string, variant Variant) []CommandOccurrence
FindCommandOccurrences extracts all command positions from a shell script. It returns occurrences with precise byte offsets for each command found.
type FileCreationInfo ¶
type FileCreationInfo struct {
// TargetPath is the absolute path to the target file.
TargetPath string
// Content is the literal content to write.
Content string
// ChmodMode is the octal chmod mode (e.g., 0o755, 0o644), or 0 if no chmod.
ChmodMode uint16
// IsAppend is true if ALL writes in the chain use >> (append) mode.
// If true, converting to COPY would lose existing file content.
// A later > (overwrite) clears this flag since content no longer depends on existing data.
IsAppend bool
// HasUnsafeVariables is true if the script uses variables that cannot be
// converted to COPY heredoc (e.g., shell variables, command substitution).
HasUnsafeVariables bool
// PrecedingCommands contains commands before the file creation (for mixed scripts).
// Empty if file creation is at the start or script is pure file creation.
PrecedingCommands string
// RemainingCommands contains commands after the file creation (for mixed scripts).
// Empty if file creation is at the end or script is pure file creation.
RemainingCommands string
}
FileCreationInfo describes a detected file creation pattern in a shell script. This is used to coordinate between prefer-copy-heredoc and prefer-run-heredoc rules.
func DetectFileCreation ¶
func DetectFileCreation(script string, variant Variant, knownVars func(name string) bool) *FileCreationInfo
DetectFileCreation analyzes a shell script for file creation patterns. Returns nil if the script is not primarily a file creation operation.
Detected patterns:
- echo "content" > /path/to/file
- echo "content" >> /path/to/file (append)
- cat <<EOF > /path/to/file ... EOF
- printf "content" > /path/to/file (limited support)
Also detects chmod chaining: echo "x" > /file && chmod 0755 /file
The knownVars function is called to check if a variable is a known ARG/ENV. If nil, all variables are considered unsafe.
type PackageInstallInfo ¶
type PackageInstallInfo struct {
Manager PackageManager
Packages []string
}
PackageInstallInfo represents a detected package installation.
func ExtractPackageInstalls ¶
func ExtractPackageInstalls(script string, variant Variant) []PackageInstallInfo
ExtractPackageInstalls parses a shell script and extracts package installations.
type PackageManager ¶
type PackageManager string
PackageManager identifies a system package manager.
const ( PackageManagerApt PackageManager = "apt" PackageManagerApk PackageManager = "apk" PackageManagerYum PackageManager = "yum" PackageManagerDnf PackageManager = "dnf" PackageManagerZypper PackageManager = "zypper" PackageManagerPacman PackageManager = "pacman" PackageManagerEmerge PackageManager = "emerge" PackageManagerUnknown PackageManager = "" )
type Variant ¶
type Variant int
Variant represents a shell variant for parsing.
const ( // VariantBash is the GNU Bash shell (default for Docker). VariantBash Variant = iota // VariantPOSIX is the POSIX-compliant shell (sh, dash, ash). VariantPOSIX // VariantMksh is the MirBSD Korn Shell. VariantMksh // VariantNonPOSIX represents shells that are not POSIX-compatible. // When this variant is active, shell-specific linting rules are disabled. // Examples: powershell, cmd, pwsh VariantNonPOSIX )
func VariantFromShell ¶
VariantFromShell returns the appropriate Variant for a shell name. Common shell mappings:
- bash -> VariantBash
- sh, dash, ash -> VariantPOSIX
- mksh, ksh -> VariantMksh
- zsh -> VariantBash (closest approximation)
- powershell, pwsh, cmd -> VariantNonPOSIX (disables shell linting)
- unknown -> VariantBash (safe default)
func VariantFromShellCmd ¶
VariantFromShellCmd returns the appropriate Variant from a SHELL command array. The first element is typically the shell path (e.g., ["/bin/bash", "-c"]).
func (Variant) IsNonPOSIX ¶
IsNonPOSIX returns true if this variant represents a non-POSIX shell. When true, shell-specific linting rules should be disabled because the shell syntax is incompatible with POSIX/Bash parsing.
type WrapperArg ¶
type WrapperArg struct {
// Arg is the syntax.Word representing this argument
Arg *syntax.Word
// Index is the position in the args slice
Index int
// Name is the base name of the command (path.Base applied)
Name string
// RemainingArgs are the args after this command
RemainingArgs []*syntax.Word
}
WrapperArg represents a potential command argument found within wrapper arguments.