Julien Danjou

Nov 6, 2025

8 min

read

Origin and Evolution of the Globstar

Stay ahead in CI/CD

The latest blog posts, release news, and automation tips straight in your inbox

Stay ahead in CI/CD

The latest blog posts, release news, and automation tips straight in your inbox

Discover how the double-asterisk recursive glob pattern evolved: from Zsh’s early 1990s innovation to Bash’s globstar and beyond, spreading into Python, Ruby, Git, and modern build tools.

If you have never met it, the double-asterisk ** is a globbing pattern that matches files and directories recursively through subdirectories, unlike a single * , which only matches within one directory. This recursive wildcard was not part of the original Unix wildcard set and is not standardized by POSIX. Instead, ** emerged as an extension introduced at different times in various shells and programming environments, each with slightly different behaviors.

While adding global matching support to Mergify, I realized I'd been using ** for years without ever knowing who invented it. That small curiosity evolved into a deep dive into its roots, a story that spans early Unix shells, Bash, Python, Ruby, and beyond.

Early Origins in Unix Shells (Zsh)

Z shell (zsh) was a pioneer in implementing recursive globs. Zsh introduced the concept of recursive globbing around 1990, with an initial experimental syntax (reportedly using patterns like .... in zsh 2.0 or **** zsh 2.1 back in 1991 according to Stéphane Chazelas). This evolved into the now-familiar **/ syntax by zsh version 2.2 in 1992, which allowed **/ to match "0 or more subdirectories" in a path. In zsh's implementation, **/ is treated as a single token meaning "descend into subdirectories recursively" (while a standalone ** not followed by / wasn't special in early zsh, again, according to Chazelas). Zsh made recursive globbing a built-in feature (enabled by default), making commands like ls **/*.txt list all .txt files in the current directory and all subdirectories without needing external tools like find.

Notably, this innovation was unique to zsh at the time: classic Bourne shell, C shell (csh), and early Korn shell did not have **. Zsh's early adoption in the 1990s paved the way for other shells and tools to follow. (It's worth mentioning that because ** was not in POSIX, its behavior was defined independently by each implementation.)

KornShell and the "globstar" Option

The KornShell (ksh), specifically ksh93, introduced its own version of the double-star glob about a decade later. According to historical notes, ksh93 added support for ** around 2003, naming the feature "globstar." This was done independently of zsh's work. In fact, the Korn shell author (David Korn) believed he had invented the idea, not realizing zsh had it much earlier. Ksh93's globstar (enabled via set -o globstar or set -G) had some semantic differences from zsh's (for example, ksh93 treats ** and **/ slightly differently and does not follow symlinks by default when recursing). The term "globstar" for the double-asterisk option originates from this ksh93 implementation.

From: David Korn <dgkorn-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Recursive glob (Was: [1003.1(2013)/Issue7+TC1 0001031]: Add
 -iname (case-insensitive name search) to the find utility.)
Date: Wed, 9 Mar 2016 18:18:02 -0500
Cc: Joerg Schilling, schwarze-mcycREo4Un4@public.gmane.org,
  dwheeler-W7BaNdmbDms@public.gmane.org,
  austin-group-l-7882/jkIBncuagvECLh61g@public.gmane.org
To: Stephane CHAZELAS

As far as I know, ** did not exist in other shells when I added it in 2003.


On Tue, Mar 8, 2016 at 9:17 AM, Stephane CHAZELAS <
stephane.chazelas-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:

> 2016-03-08 14:50:08 +0100, Joerg Schilling:
> [...]
> > And with the future -call, a "find" shell builtin could be even better
> than
> > those zsh globs. BTW: ** is also in ksh, do you know who invented the
> feature?
> [...]
>
> That was zsh in the early 90s.
>
> It's been copied since with a lot of variation by ksh93, bash,
> yash, tcsh and fish.
>
> The glob qualifier part (like (.mm-3) for regular files
> modified later than 3 minutes ago) and generic recursive glob
> (like (^.svn/)#*.txt for any level of subdir, but skipping .svn
> directories) have not been copied to other shells yet (even
> though IMO they are an essential companion to **).
>
> details at
>
> https://unix.stackexchange.com/questions/62660/the-result-of-ls-ls-and-ls/62665#62665
>
> --
> Stephane

(Yes, I had to use NNTP to fetch this old message 😅)

Ksh93's addition in 2003 helped popularize the concept in the UNIX community beyond just zsh users. It also meant that by the early 2000s, two major shells (zsh and ksh) supported ** recursion, albeit in non-identical ways.

Adoption in Bash (Globstar in Bash 4)

The widely used Bash shell was slower to adopt **. Bash finally introduced the feature in version 4.0 (released in 2009), under an option named globstar, which closely followed the design of ksh93. With globstar enabled (shopt -s globstar), Bash treats ** as a wildcard for "zero or more directories" when it appears as a path component. Early Bash implementations also followed symlinks by default when recursing, which was changed in Bash 5.0.

For example, bash$ echo **/foo.txt would find any foo.txt in the current directory or any subdirectory (when globstar is on). By default, Bash leaves globstar disabled to avoid surprising behavior in legacy scripts. (Enabling it can potentially change the meaning of patterns in existing scripts in dangerous ways, so it remains opt-in in Bash.)

Interestingly, Bash's implementation was more directly influenced by Korn shell's version than zsh's. The Bash developers copied ksh93's approach and even its name, "globstar," including the decision to keep it off by default. Bash 4's release in 2009 made ** available to a vast user base, firmly cementing the double-star pattern in mainstream use. By this time, the feature had also appeared in a few other shells: for example, tcsh added a globstar option (off by default) in v6.17.01 in 2010 and the minimal POSIX-like shell yash implemented ** under an extended glob mode in its 2.0 version back in 2008. The modern fish shell likewise supports ** (enabled by default in fish, though its recursion semantics differ somewhat). However, no formal POSIX standard covers **, so each shell's manual documents its own nuances.

Spread to Programming Languages and Tools

Outside of interactive shells, the double-asterisk pattern also spread into programming language libraries and tools in the 2000s, as recursive file matching became a common need, but with wildly different meaning:

  • Ruby: The Ruby language has long supported ** in its file globbing methods. Ruby's Dir.glob and File.fnmatch recognize ** as "match directories recursively.”"For instance, Dir["**/*.rb"] will find Ruby files in the working directory and all subfolders. This was already documented in Ruby 1.8 (mid-2000s), and likely existed even earlier, meaning Ruby adopted the ** convention relatively early in its development. This made recursive file operations very convenient in Ruby's build scripts and tools (e.g., Rake tasks commonly use ** patterns).

  • Python: In Python's standard library, globbing with ** was introduced more recently. Originally, Python's glob.glob() did not treat ** specially. In Python 3.5 (2015) the glob module added a recursive=True flag which enables ** behavior. With Python 3.5+, you can do glob.glob('**/*.txt', recursive=True) (or use pathlib.Path.glob("**/*.txt")) to recursively match files in subdirectories. This brought Python's globbing in line with the shell convention. Python's documentation notes that ** matches "0 or more directories" when recursion is enabled. (Earlier Python code often had to use os.walk or manual recursion; the addition of ** support simplified many file searching tasks.)

  • JavaScript/Node.js: JavaScript itself doesn't have built-in globbing, but the Node.js ecosystem widely uses libraries (like minimatch and glob) that understand **. By the 2010s, these libraries made ** a commonplace syntax in config files and build tools (for example, in Webpack or Grunt configuration, a pattern like src/**/*.js matches all JS files under src). In practice, Node’s globbing libraries modeled their syntax on the shell conventions, so developers in that ecosystem also became familiar with **.

  • Java: Java introduced a globbing facility in its NIO file APIs (Java 7, released 2011). The PathMatcher class with "glob:" patterns supports ** as "zero or more directory levels" in a path pattern. For example, a glob pattern like **/*.java will match all .java files recursively. This demonstrates how the double-star idea is being incorporated into even strongly typed languages through library support.

  • Other Languages: Many other modern languages and frameworks have adopted ** for recursive matches. .NET, for instance, provides globbing in libraries (e.g. Microsoft's FileSystemGlobbing in .NET) that include ** as the recursive-directory wildcard. Go's filepath.Glob supports patterns but notably did not support ** by default in the standard library; however, third-party Go libraries (like bmatcuk/doublestar) were created to add double-star support. In summary, as the ** pattern became popular in shells and build tools, languages often followed suit either natively or via add-on libraries.

  • Build and Configuration Tools: The double-asterisk quickly became a de facto standard in build systems and config files. For example, Apache Ant (a Java build tool first released around 2000) used ** in its file set patterns from the start, e.g., <fileset includes="**/*.java" /> to include all Java files in all subdirectories. This likely helped popularize the syntax among Java and XML build script authors. Similarly, version control ignore files like .gitignore adopted ** for matching directory hierarchies. Git's ignore pattern documentation specifies that ** can match "any number of directories" (allowing ignore rules like logs/** to ignore all files under any logs subfolder). By the mid-2000s, anyone working with Git, Ant, or similar tools encountered ** as a natural extension of glob syntax.

Standardization and Widespread Adoption

Despite its lack of official standardization, the ** recursive glob pattern effectively became widely accepted across different ecosystems by the 2010s. Today, many users assume ** is a normal part of "Unix-style" glob syntax because of how prevalent it is. However, it's worth remembering that it originated as an innovation in specific shells rather than in early Unix itself. Each implementation did it a bit differently until usage converged: zsh's approach influenced others, ksh's implementation coined the term "globstar," and Bash’s large user base spread it further (though it must be enabled). Now, the pattern is commonplace in programming contexts (from Python and Ruby scripts to build tools and config files) and is supported by numerous libraries and frameworks.

In summary, the double-asterisk ** first appeared in Zsh around 1990-1992 as an extended globbing feature. It was later independently implemented in ksh93 (circa 2003) as "globstar", then adopted by Bash 4.0 in 2009. From there, it became a de facto standard pattern, spreading to high-level languages (Ruby, Python 3.5+, JavaScript libraries, etc.) and configuration files. Nearly three decades after its humble origin in zsh, ** recursive globbing is now an indispensable tool for developers, symbolizing the ability to traverse directory trees with a simple wildcard – a clear example of a once-proprietary innovation turned into a cross-language convention.

If I had to hand out medals, I'd give the innovation award to Zsh for inventing it, the marketing award to Ksh for naming it "globstar," and the distribution award to Bash for making it available to everyone's terminal. 🏅

Stay ahead in CI/CD

The latest blog posts, release news, and automation tips straight in your inbox

Stay ahead in CI/CD

The latest blog posts, release news, and automation tips straight in your inbox

Recommended blogposts

Nov 5, 2025

5 min

read

Shadow Shipping: How We Double-Executed Code to Ship Safely

How do you ship risky code without crossing your fingers? In this post, we explain how he ran old and new logic in parallel (“shadow shipping”) to validate behavior in production before rollout. Learn how this simple pattern turned feature-flag anxiety into data-driven confidence.

Julian Maurin

Nov 5, 2025

5 min

read

Shadow Shipping: How We Double-Executed Code to Ship Safely

How do you ship risky code without crossing your fingers? In this post, we explain how he ran old and new logic in parallel (“shadow shipping”) to validate behavior in production before rollout. Learn how this simple pattern turned feature-flag anxiety into data-driven confidence.

Julian Maurin

Nov 5, 2025

5 min

read

Shadow Shipping: How We Double-Executed Code to Ship Safely

How do you ship risky code without crossing your fingers? In this post, we explain how he ran old and new logic in parallel (“shadow shipping”) to validate behavior in production before rollout. Learn how this simple pattern turned feature-flag anxiety into data-driven confidence.

Julian Maurin

Nov 5, 2025

5 min

read

Shadow Shipping: How We Double-Executed Code to Ship Safely

How do you ship risky code without crossing your fingers? In this post, we explain how he ran old and new logic in parallel (“shadow shipping”) to validate behavior in production before rollout. Learn how this simple pattern turned feature-flag anxiety into data-driven confidence.

Julian Maurin

Oct 29, 2025

6 min

read

Why PostgreSQL Ignored Our Index (and What the Planner Was Thinking)

PostgreSQL doesn’t "ignore" your indexes, it just does the math differently. We dive into how the planner weighs cost, why it sometimes chooses sequential scans, and how we tuned our queries to make peace with it.

Fabien Martinet

Oct 29, 2025

6 min

read

Why PostgreSQL Ignored Our Index (and What the Planner Was Thinking)

PostgreSQL doesn’t "ignore" your indexes, it just does the math differently. We dive into how the planner weighs cost, why it sometimes chooses sequential scans, and how we tuned our queries to make peace with it.

Fabien Martinet

Oct 29, 2025

6 min

read

Why PostgreSQL Ignored Our Index (and What the Planner Was Thinking)

PostgreSQL doesn’t "ignore" your indexes, it just does the math differently. We dive into how the planner weighs cost, why it sometimes chooses sequential scans, and how we tuned our queries to make peace with it.

Fabien Martinet

Oct 29, 2025

6 min

read

Why PostgreSQL Ignored Our Index (and What the Planner Was Thinking)

PostgreSQL doesn’t "ignore" your indexes, it just does the math differently. We dive into how the planner weighs cost, why it sometimes chooses sequential scans, and how we tuned our queries to make peace with it.

Fabien Martinet

Oct 23, 2025

5 min

read

The Magic (and Mayhem) Behind Our Config Deprecation Transformers

We built a "self-healing" system that fixes deprecated configs by opening PRs automatically. It worked like magic, until it didn't. Here's what we learned about the thin line between elegant automation and uncontrollable complexity.

Guillaume Risbourg

Oct 23, 2025

5 min

read

The Magic (and Mayhem) Behind Our Config Deprecation Transformers

We built a "self-healing" system that fixes deprecated configs by opening PRs automatically. It worked like magic, until it didn't. Here's what we learned about the thin line between elegant automation and uncontrollable complexity.

Guillaume Risbourg

Oct 23, 2025

5 min

read

The Magic (and Mayhem) Behind Our Config Deprecation Transformers

We built a "self-healing" system that fixes deprecated configs by opening PRs automatically. It worked like magic, until it didn't. Here's what we learned about the thin line between elegant automation and uncontrollable complexity.

Guillaume Risbourg

Oct 23, 2025

5 min

read

The Magic (and Mayhem) Behind Our Config Deprecation Transformers

We built a "self-healing" system that fixes deprecated configs by opening PRs automatically. It worked like magic, until it didn't. Here's what we learned about the thin line between elegant automation and uncontrollable complexity.

Guillaume Risbourg

Curious where your CI is slowing you down?

Try CI Insights — observability for CI teams.

Curious where your CI is slowing you down?

Try CI Insights — observability for CI teams.

Curious where your CI is slowing you down?

Try CI Insights — observability for CI teams.

Curious where your CI is slowing you down?

Try CI Insights — observability for CI teams.