Brace Expansion and Globbing

Quote

“The order of expansions is: brace expansion, tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, word splitting, and filename expansion.”

  • Bash Reference Manual, GNU

Brace expansion and globbing solve different problems. Brace expansion manufactures words before the shell looks at the filesystem. Globbing resolves wildcard patterns against the filesystem after earlier expansions have already finished. PowerShell reaches similar outcomes, but it does so with explicit iteration and cmdlet-side filtering instead of shell-level brace and filename expansion.


flowchart TD
    A["Shell command line"] --> B["Brace expansion<br>{a,b,c} / {1..5}"]
    B --> C["Tilde expansion<br>~user"]
    C --> D["Parameter expansion<br>$VAR / ${VAR}"]
    D --> E["Command substitution<br>$(cmd)"]
    E --> F["Arithmetic expansion<br>$((expr))"]
    F --> G["Word splitting"]
    G --> H["Filename expansion<br>(globbing)<br>*.py / **/*.sql"]
    H --> I["Command executed<br>with final arguments"]

Linux brace expansion tools

Brace expansion is pure text generation. The shell never checks whether a file already exists while it expands the braces, so these patterns are best when the names are known ahead of time.

Linux | brace expansion | commands

Use these forms when you need compact, deterministic argument generation rather than filesystem discovery. Treat each H4 as a standalone snippet in a disposable working directory.

Create a nested directory tree in one command

mkdir -p is silent on success, so the second command verifies the exact tree that brace expansion generated.

Run the commands in this section to create a nested directory tree in one command.

mkdir -p data/{bronze,silver,gold}/{raw,staging,final}

Run the commands in this section to create a nested directory tree in one command.

find data -type d | sort
data
data/bronze
data/bronze/final
data/bronze/raw
data/bronze/staging
data/gold
data/gold/final
data/gold/raw
data/gold/staging
data/silver
data/silver/final
data/silver/raw
data/silver/staging

Generate a numeric sequence of filenames

Zero padding is preserved when the range starts with a zero-padded value. echo receives the already-expanded filenames as separate arguments.

Run the commands in this section to generate a numeric sequence of filenames.

echo file{001..012}.parquet
file001.parquet file002.parquet file003.parquet file004.parquet file005.parquet file006.parquet file007.parquet file008.parquet file009.parquet file010.parquet file011.parquet file012.parquet

Back up a file before editing

cp is also silent on success, so create the source file first, then verify that the backup exists after the copy.

Run the commands in this section to back up a file before editing.

printf 'key: value\n' > config.yaml

Run the commands in this section to back up a file before editing.

cp config.yaml{,.bak}

Run the commands in this section to back up a file before editing.

find . -maxdepth 1 -type f -printf '%P\n' | sort
config.yaml
config.yaml.bak

Generate a stepped numeric range

The optional third term is the step. This is useful for partitions, checkpoints, or fixed-size batches.

Run the commands in this section to generate a stepped numeric range.

echo partition_{0..100..10}
partition_0 partition_10 partition_20 partition_30 partition_40 partition_50 partition_60 partition_70 partition_80 partition_90 partition_100

Create versioned copies from one source file

Brace expansion can generate the destination names, but a single cp model.pkl{,.v1,.v2,.backup} call is not valid because cp accepts only one non-directory destination. A loop keeps the source fixed while using brace expansion to enumerate the targets.

Run the commands in this section to create versioned copies from one source file.

: > model.pkl

Run the commands in this section to create versioned copies from one source file.

for target in model.pkl{.v1,.v2,.backup}; do cp model.pkl "$target"; done

Run the commands in this section to create versioned copies from one source file.

find . -maxdepth 1 -type f -name 'model.pkl*' -printf '%P\n' | sort
model.pkl
model.pkl.backup
model.pkl.v1
model.pkl.v2

Use this lookup table as a quick reference after the verified examples.

SyntaxExampleMeaning
{a,b,c}echo {foo,bar,baz}Comma-separated list of words
{n..m}echo {1..5}Inclusive integer range
{n..m..s}echo {0..20..5}Inclusive stepped range
{001..010}echo {001..010}Zero-padded range
{a..z}echo {a..z}Alphabetic range
prefix{a,b}suffixecho file{A,B}.csvShared prefix and suffix
{a,{b,c}}echo {x,{y,z}}Nested brace expansion

Linux globbing tools

Standard wildcards work without any shopt option. extglob, globstar, failglob, dotglob, and nocaseglob change how matching works, so they need deliberate enablement.

Linux | shopt | globbing options

These options change shell behavior. When the enabling command is silent, verify it explicitly before relying on the option. Treat each H4 as a standalone snippet in a disposable working directory.

No-match behavior must be chosen deliberately

Default Bash leaves an unmatched pattern literal, nullglob removes it, and failglob turns it into an error. Pick one before using wildcards in rm, mv, or loops, because an empty match set changes the blast radius of the command.

Enable extended globbing patterns

extglob turns on the quantified and negated operators that standard globbing does not have.

Run the commands in this section to enable extended globbing patterns.

shopt -s extglob

Run the commands in this section to enable extended globbing patterns.

shopt extglob
extglob        	on

These operators are now available:

OperatorMeaning
?(pattern)Zero or one match
*(pattern)Zero or more matches
+(pattern)One or more matches
@(pattern)Exactly one match
!(pattern)Anything that does not match

List all files excluding specific extensions

This example stages a disposable directory, enables extglob, and prints the expansion so you can inspect the match set before sending it to another command.

Run the commands in this section to list all files excluding specific extensions.

mkdir extglob-demo && cd extglob-demo
: > config.yaml
: > main.py
: > requirements.txt
: > schema.sql
: > debug.log
: > cache.tmp
shopt -s extglob

Run the commands in this section to list all files excluding specific extensions.

printf '%s\n' !(*.log|*.tmp) | sort
config.yaml
main.py
requirements.txt
schema.sql

Delete all files except one

For destructive patterns, preview the expansion first in a disposable directory. Once the preview looks correct, run the delete and verify the result explicitly.

Run the commands in this section to delete all files except one.

mkdir delete-demo && cd delete-demo
: > important.txt
: > draft.txt
: > notes.md
: > scratch.tmp
shopt -s extglob

Run the commands in this section to delete all files except one.

printf '%s\n' !(important.txt) | sort
draft.txt
notes.md
scratch.tmp

Run the commands in this section to delete all files except one.

rm !(important.txt)

Run the commands in this section to delete all files except one.

find . -maxdepth 1 -type f -printf '%P\n' | sort
important.txt

Enable recursive double-star globbing

globstar changes ** from an ordinary path wildcard into a recursive directory traversal operator.

Run the commands in this section to enable recursive double-star globbing.

shopt -s globstar

Run the commands in this section to enable recursive double-star globbing.

shopt globstar
globstar       	on

Recursively match files by extension

This example uses printf instead of ls so the expansion result is exact and not reformatted into columns.

Run the commands in this section to recursively match files by extension.

mkdir -p tree/etl/utils tree/models tree/tests
: > tree/etl/pipeline.py
: > tree/etl/utils/helpers.py
: > tree/models/train.py
: > tree/tests/test_pipeline.py
: > tree/models/train.sql
cd tree
shopt -s globstar

Run the commands in this section to recursively match files by extension.

printf '%s\n' **/*.py
etl/pipeline.py
etl/utils/helpers.py
models/train.py
tests/test_pipeline.py

Count lines across all SQL files recursively

wc -l is a good fit when a recursive glob already yields the exact files you want. If the tree is huge or you need extra predicates, move to find instead of expanding everything in the shell.

Run the commands in this section to count lines across all SQL files recursively.

mkdir -p sql-demo/queries sql-demo/schema
printf 'select 1;\nselect 2;\n' > sql-demo/queries/daily_agg.sql
printf 'create table t1;\ncreate table t2;\ncreate table t3;\n' > sql-demo/queries/index_weights.sql
printf 'begin;\n' > sql-demo/schema/init.sql
cd sql-demo
shopt -s globstar

Run the commands in this section to count lines across all SQL files recursively.

wc -l **/*.sql
 2 queries/daily_agg.sql
 3 queries/index_weights.sql
 1 schema/init.sql
 6 total

Enable failglob to stop no-match pass-through

failglob is silent when enabled, so verify the state before depending on it in a script header.

Run the commands in this section to enable failglob to stop no-match pass-through.

shopt -s failglob

Run the commands in this section to enable failglob to stop no-match pass-through.

shopt failglob
failglob       	on

Use this lookup table as a reference for shopt itself.

FlagSyntaxMeaning
-sshopt -s <option>Enable an option
-ushopt -u <option>Disable an option
-pshopt -pPrint shell options as reusable commands
-qshopt -q <option>Return success if enabled, failure if disabled

Linux | globbing | standard wildcard patterns

The table at the end is a compact reminder. The H4 entries here show what the wildcard actually expands to.

Match any string with *

* matches zero or more characters inside one path component.

Run the commands in this section to match any string with *.

mkdir wildcard-star && cd wildcard-star
: > data.csv
: > sales_2025.csv
: > report.txt

Run the commands in this section to match any string with *.

printf '%s\n' *.csv | sort
data.csv
sales_2025.csv

Match exactly one character with ?

? matches one character, so file10.txt is excluded because it needs two characters after file.

Run the commands in this section to match exactly one character with ?.

mkdir wildcard-question && cd wildcard-question
: > file1.txt
: > file2.txt
: > fileA.txt
: > file10.txt

Run the commands in this section to match exactly one character with ?.

printf '%s\n' file?.txt | sort
file1.txt
file2.txt
fileA.txt

Match sets and ranges with []

Character classes can name explicit sets such as [abc] or ranges such as [0-9]. This example uses a numeric range.

Run the commands in this section to match sets and ranges with [].

mkdir wildcard-range && cd wildcard-range
: > log1.txt
: > log2.txt
: > logA.txt

Run the commands in this section to match sets and ranges with [].

printf '%s\n' log[0-9].txt | sort
log1.txt
log2.txt

Exclude starting characters with [!...]

[!set] negates a single character position. Here it excludes files whose first character is a digit.

Run the commands in this section to exclude starting characters with [!...].

mkdir wildcard-negated && cd wildcard-negated
: > alpha.csv
: > beta.csv
: > 1-summary.csv

Run the commands in this section to exclude starting characters with [!...].

printf '%s\n' [!0-9]*.csv | sort
alpha.csv
beta.csv

Include dotfiles with dotglob

By default, * skips hidden files. Enabling dotglob changes that behavior for the current shell.

Run the commands in this section to include dotfiles with dotglob.

mkdir dotglob-demo && cd dotglob-demo
: > .env
: > .gitignore
: > report.csv
printf 'default\n'
printf '%s\n' * | sort
shopt -s dotglob
printf 'enabled\n'
printf '%s\n' * | sort
printf 'state\n'
shopt dotglob
default
report.csv
enabled
.env
.gitignore
report.csv
state
dotglob        	on

Use this lookup table as a quick reminder after the runnable examples.

PatternMeaningExample
*Zero or more characters in one path component*.csv
?Exactly one characterfile?.txt
[abc]One character from an explicit set[abc].sh
[a-z]One character from a rangelog[0-9].txt
[!abc]One character not in a set[!0-9]*.csv
**Zero or more directories when globstar is on**/*.py

PowerShell brace expansion tools

PowerShell does not perform brace expansion in the shell. The equivalent result is explicit iteration with arrays, ranges, loops, and string formatting.

PowerShell | arrays and loops | argument generation

These examples generate the same kinds of path sets as Bash brace expansion, but the shell is not rewriting the command line beforehand. Treat each H4 as a standalone snippet in a disposable working directory.

Create a nested directory tree using nested loops

New-Item is silent only because the pipeline sends its objects to Out-Null, so verify the resulting tree explicitly.

Run the commands in this section to create a nested directory tree using nested loops.

$tiers = 'bronze','silver','gold'
$zones = 'raw','staging','final'
foreach ($tier in $tiers) {
    foreach ($zone in $zones) {
        New-Item -ItemType Directory -Path "data/$tier/$zone" -Force | Out-Null
    }
}

Run the commands in this section to create a nested directory tree using nested loops.

Get-ChildItem -Path data -Directory -Recurse |
    Sort-Object FullName |
    ForEach-Object { $_.FullName.Substring($PWD.Path.Length + 1) }
data\bronze
data\bronze\final
data\bronze\raw
data\bronze\staging
data\gold
data\gold\final
data\gold\raw
data\gold\staging
data\silver
data\silver\final
data\silver\raw
data\silver\staging

Generate zero-padded filenames

Use the range operator for the integers, then format them into fixed-width strings.

Run the commands in this section to generate zero-padded filenames.

1..12 | ForEach-Object { 'file{0:D3}.parquet' -f $_ }
file001.parquet
file002.parquet
file003.parquet
file004.parquet
file005.parquet
file006.parquet
file007.parquet
file008.parquet
file009.parquet
file010.parquet
file011.parquet
file012.parquet

Back up a file before editing

There is no brace shorthand here. Create or select the source, copy it, and then verify the result directly.

Run the commands in this section to back up a file before editing.

Set-Content -Path config.yaml -Value 'key: value'

Run the commands in this section to back up a file before editing.

Copy-Item config.yaml config.yaml.bak

Run the commands in this section to back up a file before editing.

Get-ChildItem -Path config.yaml* |
    Sort-Object Name |
    Select-Object -ExpandProperty Name
config.yaml
config.yaml.bak

Use this lookup table as a translation aid between Bash intent and PowerShell syntax.

Bash formPowerShell equivalentMeaning
{a,b,c}'a','b','c' | ForEach-Object { ... }Enumerate a literal set
{n..m}n..m | ForEach-Object { ... }Enumerate a numeric range
{n..m..s}for ($i=n; $i -le m; $i+=s) { ... }Enumerate a stepped range
{001..010}1..10 | ForEach-Object { '{0:D3}' -f $_ }Zero-pad during formatting
prefix{a,b}suffix'a','b' | ForEach-Object { "file$_.csv" }Add a shared prefix and suffix

PowerShell globbing tools

Get-ChildItem performs enumeration and filtering inside the cmdlet. -Filter is the provider/native filter and accepts one pattern. -Include and -Exclude use PowerShell wildcard semantics and work against the child items that the cmdlet actually enumerates.

PowerShell | Get-ChildItem | recursive file matching

When the cmdlet is silent or returns objects you suppress, add an explicit verification command so the note proves what happened. Treat each H4 as a standalone snippet in a disposable working directory.

Use -LiteralPath when the name is data, not a pattern

-Path, -Filter, -Include, and -Exclude all treat wildcard characters as patterns. Reach for -LiteralPath when a real filename contains [, ], *, or ?, and prefer -Filter over pipeline-side filtering when the provider supports it because it narrows enumeration earlier.

Recursively list all files of a given type

For a single wildcard, -Filter is the cleanest and usually fastest choice.

Run the commands in this section to recursively list all files of a given type.

New-Item -ItemType Directory -Path 'tree/etl','tree/models' -Force | Out-Null
Set-Content -Path 'tree/etl/pipeline.py' -Value 'print(1)'
Set-Content -Path 'tree/etl/utils.py' -Value 'print(2)'
Set-Content -Path 'tree/models/train.py' -Value 'print(3)'
Set-Content -Path 'tree/models/train.sql' -Value 'select 1;'
Set-Location tree

Run the commands in this section to recursively list all files of a given type.

Get-ChildItem -Path . -Filter *.py -Recurse -File |
    Sort-Object FullName |
    ForEach-Object { $_.FullName.Substring($PWD.Path.Length + 1) }
etl\pipeline.py
etl\utils.py
models\train.py

Recursively list files matching multiple extensions

For multiple patterns, switch to -Include and make sure the path points at children by using .\* or -Recurse.

Run the commands in this section to recursively list files matching multiple extensions.

New-Item -ItemType Directory -Path 'tree/etl','tree/models' -Force | Out-Null
Set-Content -Path 'tree/etl/pipeline.py' -Value 'print(1)'
Set-Content -Path 'tree/etl/utils.py' -Value 'print(2)'
Set-Content -Path 'tree/models/train.py' -Value 'print(3)'
Set-Content -Path 'tree/models/train.sql' -Value 'select 1;'
Set-Location tree

Run the commands in this section to recursively list files matching multiple extensions.

Get-ChildItem -Path .\* -Include *.py,*.sql -Recurse -File |
    Sort-Object FullName |
    ForEach-Object { $_.FullName.Substring($PWD.Path.Length + 1) }
etl\pipeline.py
etl\utils.py
models\train.py
models\train.sql

Exclude specific extensions from a directory listing

-Exclude uses the same wildcard engine as -Include. Point the path at the child items you want filtered.

Run the commands in this section to exclude specific extensions from a directory listing.

Set-Content -Path config.yaml -Value 'key: value'
Set-Content -Path pipeline.py -Value 'print(1)'
Set-Content -Path requirements.txt -Value 'requests'
Set-Content -Path debug.log -Value 'log'
Set-Content -Path cache.tmp -Value 'tmp'

Run the commands in this section to exclude specific extensions from a directory listing.

Get-ChildItem -Path .\* -File -Exclude *.log,*.tmp |
    Sort-Object Name |
    Select-Object -ExpandProperty Name
config.yaml
pipeline.py
requirements.txt

Preview and then delete all files except one

PowerShell has no !(pattern) operator. Build the keep rule with Where-Object, preview the delete with -WhatIf, and only then run the real removal. The captured preview output includes the temporary root used during execution.

Run the commands in this section to preview and then delete all files except one.

New-Item -ItemType Directory -Path delete-demo -Force | Out-Null
Set-Content -Path 'delete-demo/important.txt' -Value 'keep'
Set-Content -Path 'delete-demo/draft.txt' -Value 'remove'
Set-Content -Path 'delete-demo/notes.md' -Value 'remove'
Set-Location delete-demo

Run the commands in this section to preview and then delete all files except one.

Get-ChildItem -Path . -File |
    Where-Object { $_.Name -ne 'important.txt' } |
    Remove-Item -WhatIf
What if: Performing the operation "Remove File" on target "C:\Users\aperi\AppData\Local\Temp\brace-glob-ps-02fe4964-c038-4f67-a6dd-f028a7f9e750\delete-demo\draft.txt".
What if: Performing the operation "Remove File" on target "C:\Users\aperi\AppData\Local\Temp\brace-glob-ps-02fe4964-c038-4f67-a6dd-f028a7f9e750\delete-demo\notes.md".

Run the commands in this section to preview and then delete all files except one.

Get-ChildItem -Path . -File |
    Where-Object { $_.Name -ne 'important.txt' } |
    Remove-Item

Run the commands in this section to preview and then delete all files except one.

Get-ChildItem -Path . -File |
    Sort-Object Name |
    Select-Object -ExpandProperty Name
important.txt

Count lines across all SQL files recursively

PowerShell returns objects rather than a wc-style total, so build the output you want explicitly.

Run the commands in this section to count lines across all SQL files recursively.

New-Item -ItemType Directory -Path 'sql/queries','sql/schema' -Force | Out-Null
Set-Content -Path 'sql/queries/daily_agg.sql' -Value @('select 1;','select 2;')
Set-Content -Path 'sql/queries/index_weights.sql' -Value @('create table t1;','create table t2;','create table t3;')
Set-Content -Path 'sql/schema/init.sql' -Value @('begin;')

Run the commands in this section to count lines across all SQL files recursively.

$total = 0
Get-ChildItem -Path sql -Filter *.sql -Recurse -File |
    Sort-Object FullName |
    ForEach-Object {
        $count = (Get-Content $_.FullName).Count
        $total += $count
        "{0}`t{1}" -f $count, $_.FullName.Substring($PWD.Path.Length + 1)
    }
"total`t$total"
2	sql\queries\daily_agg.sql
3	sql\queries\index_weights.sql
1	sql\schema\init.sql
total	6

Use this table as a quick reference for the cmdlet parameters after the runnable examples.

ParameterMeaningNotes
-PathRoot item or wildcard path to enumerateUse .\* when -Include or -Exclude should match child names
-FilterProvider/native single-pattern filterUsually the fastest option for one wildcard
-IncludePowerShell wildcard include listOften paired with -Recurse or .\*
-ExcludePowerShell wildcard exclude listApplies to enumerated child items
-RecurseDescend into subdirectoriesCombine with -File or -Directory when you need only one type
-FileReturn only filesAvoids directory objects in later pipeline stages
-DirectoryReturn only directoriesUseful for tree verification
-DepthLimit recursion depthAvailable in Windows PowerShell 5+ and PowerShell 7+
-NameReturn names instead of full objectsUseful for quick verification

The original recommendation matrix is more useful as executable platform-specific guidance. The Linux items that are already demonstrated above stay in their feature sections; the entries here cover the defaults and distinctions that benefit from explicit verification.

Enable extglob, globstar, and failglob in scripts that depend on them

These three options change script behavior materially. Enable them immediately after set -euo pipefail in Bash scripts that use negated patterns, recursive **, or strict no-match handling.

Run the commands in this section to enable extglob, globstar, and failglob in scripts that depend on them.

shopt -s extglob globstar failglob

Run the commands in this section to enable extglob, globstar, and failglob in scripts that depend on them.

shopt extglob globstar failglob
extglob        	on
globstar       	on
failglob       	on

Keep nocaseglob and cdspell in interactive startup files

These are interactive conveniences, not core script defaults. nocaseglob widens every match in the shell, and cdspell only affects interactive cd corrections.

Run the commands in this section to keep nocaseglob and cdspell in interactive startup files.

cat > /tmp/bashrc.demo <<'EOF'
shopt -s nocaseglob
shopt -s cdspell
EOF
bash --noprofile --norc -lc 'source /tmp/bashrc.demo; shopt nocaseglob cdspell'
nocaseglob     	on
cdspell        	on

Prefer -Filter for one wildcard and -Include for multiple wildcards

-Filter is the provider/native filter and is the default choice when one pattern is enough. Use -Include when you truly need a wildcard list, and make sure the path enumerates children instead of the directory object itself.

Run the commands in this section to prefer -Filter for one wildcard and -Include for multiple wildcards.

New-Item -ItemType Directory -Path 'tree/etl','tree/models' -Force | Out-Null
Set-Content -Path 'tree/etl/pipeline.py' -Value 'print(1)'
Set-Content -Path 'tree/etl/utils.py' -Value 'print(2)'
Set-Content -Path 'tree/models/train.py' -Value 'print(3)'
Set-Content -Path 'tree/models/train.sql' -Value 'select 1;'
Set-Location tree

Run the commands in this section to prefer -Filter for one wildcard and -Include for multiple wildcards.

Get-ChildItem -Path . -Filter *.py -Recurse -File |
    Sort-Object FullName |
    ForEach-Object { $_.FullName.Substring($PWD.Path.Length + 1) }
etl\pipeline.py
etl\utils.py
models\train.py

Run the commands in this section to prefer -Filter for one wildcard and -Include for multiple wildcards.

Get-ChildItem -Path .\* -Include *.py,*.sql -Recurse -File |
    Sort-Object FullName |
    ForEach-Object { $_.FullName.Substring($PWD.Path.Length + 1) }
etl\pipeline.py
etl\utils.py
models\train.py
models\train.sql

Preview recursive deletes with -WhatIf

Remove-Item is silent on success and destructive on failure. A preview is the only safe way to confirm the target set before the delete runs. The captured preview output includes the temporary root used during execution.

Run the commands in this section to preview recursive deletes with -WhatIf.

New-Item -ItemType Directory -Path delete-demo -Force | Out-Null
Set-Content -Path 'delete-demo/important.txt' -Value 'keep'
Set-Content -Path 'delete-demo/draft.txt' -Value 'remove'
Set-Content -Path 'delete-demo/notes.md' -Value 'remove'
Set-Location delete-demo
Get-ChildItem -Path . -File |
    Where-Object { $_.Name -ne 'important.txt' } |
    Remove-Item -WhatIf
What if: Performing the operation "Remove File" on target "C:\Users\aperi\AppData\Local\Temp\brace-glob-ps-02fe4964-c038-4f67-a6dd-f028a7f9e750\delete-demo\draft.txt".
What if: Performing the operation "Remove File" on target "C:\Users\aperi\AppData\Local\Temp\brace-glob-ps-02fe4964-c038-4f67-a6dd-f028a7f9e750\delete-demo\notes.md".

Brace Expansion and Globbing Troubleshooting

Each troubleshooting item below replaces the old matrix with a concrete symptom, a runnable proof, and the correction.

Linux | brace expansion and globbing troubleshooting | common failures

Unmatched globs should fail early in scripts

If a script must stop when a pattern matches nothing, enable failglob before the command runs. The output below is the explicit error you want to see instead of a literal *.csv argument flowing downstream.

Run the commands in this section to unmatched globs should fail early in scripts.

mkdir failglob-demo && cd failglob-demo
bash --noprofile --norc -c 'shopt -s failglob; printf "%s\n" *.csv' 2>&1
bash: line 1: no match: *.csv

**/*.py stops short until globstar is enabled

Without globstar, ** is parsed as ordinary wildcard path components. The pattern below reaches only one nested level instead of the full tree.

Run the commands in this section to **/*.py stops short until globstar is enabled.

mkdir -p noglobstar-demo/a noglobstar-demo/b/c
cd noglobstar-demo
: > root.py
: > a/one.py
: > b/c/two.py
bash --noprofile --norc -c 'printf "%s\n" **/*.py'
a/one.py

!(pattern) is a syntax error until extglob is enabled

In non-interactive Bash, missing extglob produces a parse error. In interactive Bash, ! can also collide with history expansion when histexpand is on, which is why the exact message can differ.

Run the commands in this section to !(pattern) is a syntax error until extglob is enabled.

rm -rf /tmp/extglob-demo && mkdir /tmp/extglob-demo
cd /tmp/extglob-demo
touch important.txt scratch.tmp
bash --noprofile --norc -c 'printf "%s\n" !(important.txt)' 2>&1
bash: -c: line 1: syntax error near unexpected token `('
bash: -c: line 1: `printf "%s\n" !(important.txt)'

Brace expansion stays literal under /bin/sh

Brace expansion is a Bash extension, not a POSIX shell feature. If the shebang is #!/bin/sh, the text stays untouched.

Run the commands in this section to brace expansion stays literal under /bin/sh.

sh -c 'echo file{1..3}.txt'
file{1..3}.txt

PowerShell | brace expansion and globbing troubleshooting | common failures

Get-ChildItem -Include returns nothing without -Recurse or .\*

-Include filters the child items that Get-ChildItem enumerates. A bare -Path . targets the directory object itself, so the first command returns nothing. Point the path at children with .\* or recurse through the tree.

Run the commands in this section to Get-ChildItem -Include returns nothing without -Recurse or .\*.

New-Item -ItemType Directory -Path include-demo -Force | Out-Null
Set-Content -Path 'include-demo/app.py' -Value 'print(1)'
Set-Location include-demo

Run the commands in this section to Get-ChildItem -Include returns nothing without -Recurse or .\*.

$plain = Get-ChildItem -Path . -Include *.py | Select-Object -ExpandProperty Name
if (-not $plain) { '(no results)' }
(no results)

Run the commands in this section to Get-ChildItem -Include returns nothing without -Recurse or .\*.

Get-ChildItem -Path .\* -Include *.py | Select-Object -ExpandProperty Name
app.py

Brace Expansion and Globbing Cross-References