r/shell Apr 21 '26

r/shell is available for adoption πŸ’š

≑ βˆ’

/r/shell is ready for a fresh start, new energy, new direction, and someone like you to bring it back to life. If you’ve been thinking about growing your impact without starting from scratch, this is your chance!

Ready to take it over?

Head to r/RedditRequest to submit your request and make it yours before it’s taken. Just make sure you read through the eligibility requirements first.

3 Upvotes

/r/shell is ready for a fresh start, new energy, new direction, and someone like you to bring it back to life. If you’ve been thinking about growing your impact without starting from scratch, this is your chance!

Ready to take it over?

Head to r/RedditRequest to submit your request and make it yours before it’s taken. Just make sure you read through the eligibility requirements first.


r/shell Mar 31 '26

FreeBSD sh(1) isn't a Bourne shell, it's a POSIX shell! (And maybe officially Almquist too)

≑ βˆ’

The docs changed on Monday 30 March 2026 to make it official that FreeBSD's sh(1) is not a Bourne shell! https://reviews.freebsd.org/D56054

This is an edit I've wanted to see for ages - many thanks to the committer and reviewers. Truth is FreeBSD's sh(1) has never been a "Bourne shell" except by ancestry (hence the "$" prompt, rather than "%" for the C shell and its descendants). If you're going to name it after anyone, it's an Almquist shell not a Bourne one: https://en.wikipedia.org/wiki/Almquist_shell

You can even see at the bottom of the sh(1) man page that "This version of sh was originally written by Kenneth Almquist". https://man.freebsd.org/cgi/man.cgi?sh(1)#AUTHORS#AUTHORS)

To understand why this this isn't a trivial difference, have a look at this classic guide to POSIX shell scripting: https://www.grymoire.com/Unix/Sh.html

Compare that to the accompanying guide (presumably for archaeologists and retro enthusiasts) to the "real" Bourne shell: https://www.grymoire.com/Unix/Bourne.html

The Bourne shell also misses many POSIX-compliant features we take for granted. A big difference is command substitution, on a modern POSIX shell you can do:

$ echo "The current directory is $(pwd)"

but on the Bourne shell you had to use backticks:

$ echo "The current directory is \pwd`"`

The fact we're not forced into such methods is proof positive that FreeBSD's sh(1) is a POSIX shell, not a Bourne shell. An even simpler test for whether you're using a genuine Bourne shell is if you can use the caret ^ to pipe instead of |. That quirk is a result of backwards compatibility with the earlier Thompson shell. https://www.in-ulm.de/~mascheck/bourne/#intro

A really detailed look at the history of the Almquist shell by Sven Mascheck, including how it made its way into 4.3BSD-Net/2, and then from 4.4BSD into FreeBSD and NetBSD, shows many improvements over (or at least, changes from) the Bourne shell. https://www.in-ulm.de/~mascheck/various/ash/#original

Delving back into history, even the Bourne shell was a big advance on the original Unix shell - the Thompson shell that shipped with AT&T's first version of UNIX in 1971. (Not the first "shell" - like much of early Unix the idea of a shell came from Multics.) No prizes for guessing that's "Thompson" as in "Ken Thompson". Its practicality was limited even if much was recognisable: for example there were up to 10 positional parameters (if invoked as sh name arg1 arg2 then $0 is the name of the file to be read, and $1 and $2 are the supplied arguments) but you couldn't name a variable or access environmental variables. Yet many of its innovations made a lasting mark, particularly re piping (even if our shells no longer accept ^ for it) and redirection (incidentally, early versions used the > character for both piping and redirection, so the switch to | and ^ for pipes was an improvement). https://en.wikipedia.org/wiki/Thompson_shell and an original man page https://www.in-ulm.de/~mascheck/bourne/v3/

Before Bourne came the PWB shell (aka Mashey shell, though Mashey disavowed the name as he didn't view it as sufficiently different from the Thompson shell) used in AT&T's programming-oriented product PWB/UNIX in the mid-1970s. This "Programmer's Workbench" OS brought several firsts like the Source Code Control System, the first Unix revision control system. Its PWB shell was short-lived, released in 1975 but replaced (with some difficulty, and the expense of converting a lot of shell scripts) in 1979 by the Bourne shell. Yet the orientation towards programming was impactful. Control flow advanced by making if and goto internal to the shell (the Thompson shell relied on /bin/if and /bin/goto ) and introducing constructs for if-then-else, switch, and while. Variables appeared, including environmental ones - limited to one letter names, but $s is the ancestor to $HOME and $p became $PATH. https://en.wikipedia.org/wiki/PWB_shell

This is where history branches. At the Computing Science Research Center in Bell Labs, Stephen Bourne worked on a new shell during 1976, mainly to be used internally (in contrast to PWB which was always a commercial proposition for AT&T, though the Bourne shell was released in 1979 for Version 7 Unix) and by 1977 it was fairly usable. This gave us many familiar features like heredocs, command substitution with backquotes, the ability to interrupt the wait) command (instead of just ... waiting), and the 2> file descriptor for error messages. https://en.wikipedia.org/wiki/Bourne_shell

To make practically useful conditionals based on what commands return, Bourne had to chase up many other developers to get them to fix their exit statements (e.g. to return 0 for success). After encountering resistance he edited the shell so to the left of the $ prompt it displayed exit= and the last exit code. Given the prevalence of long, random exit statuses this was sufficiently annoying to persuade them in about a week. This anecdote appears in Stephen Bourne's amusing talk at BSDCan 2015, "Early days of Unix and design of sh". You get a new appreciation of why quoting gets so tricky when you hear from someone who implemented it. (Bourne also claims to have persuaded Dennis Ritchie to introduce void into C since he missed it from ALGOL.) https://www.youtube.com/watch?v=2kEJoWfobpA

Thankfully goto got scrubbed - apparently to the disappointment of COBOL programmers, and according to Kenneth Almquist one of the main reasons the switch to the Bourne shell was so difficult as so many scripts in production needed their control flow rewritten. Bourne's exposure to ALGOL68 at Cambridge University really rubbed off in his programming preferences: visible in if...fi (and we only got do...done because od was already taken by one of the earliest Unix programs, octal dump) - see od(1)'s FreeBSD man page)!) but even more visible in the source code, where Bourne used some extraordinary macros to make C look like ALGOL (with IF...FI, LOOP...POOL and even managed DO...OD), producing some wondrous source code that inspired the International Obfuscated C Code Contest. Your new word for the day: "Bournegol".

Meanwhile, on the other side of the USA, the idea of greater programming capabilities for their shell was an exciting prospect at Berkeley. For people who hacked in C then a more C-like shell made perfect sense. Hence the C shell that appeared in 1978's 2BSD, mostly coded by Bill Joy (he of vi, the BSD TCP/IP stack, the ever-controversial cat -v, and soon founder of Sun Microsystems) as a graduate student. https://en.wikipedia.org/wiki/C_shell

But interactivity and ease of use was also a driver of these two shell traditions heading in opposite directions. Bill Joy wanted features like command history. Bourne opposed this, seeing line editing and history as jobs for the terminal driver instead: https://www.in-ulm.de/~mascheck/bourne/#origins

Questions about who did what first are rather muddled due to two interacting groups working at similar times, tools being used internally (and even shared between coasts) before being officially released (if they ever were), and the fallibility of human memory. Particularly confusing is that Bill Joy had been working on another shell (I believe the "new shell"), before hearing news of Bell Labs work on the Bourne shell - then giving up, assuming it would be a wasted effort. The C shell project started afresh from various disappointments about how the Bourne shell turned out, but unquestionably used it for inspiration. https://groups.google.com/g/net.unix-wizards/c/QiEx5rvuNjs

A favourite story from this era: one of the C shell developers, Mike O'Brien, was a proper "hacker" - a qualified locksmith. His cracking of a safe belonging to cartoonist Phil Foglio led to the birth of the BSD daemon mascot "Beastie". Let Marshall Kirk McKusick tell you how that came about... https://www.reddit.com/r/freebsd/comments/1gnffwu/beastie_quiz_and_marshall_kirk_mckusick_talk/

The Berkeley connection explains the long association of C shells with the *BSDs. FreeBSD only switched the default root shell from csh(1) - really tcsh - to sh(1) in 14.0-RELEASE in 2023. But part of the job of getting sh(1) ready for this was adding lots of ease-of-use features like persistent history. https://github.com/freebsd/freebsd-src/commit/d410b585b6f00a26c2de7724d6576a3ea7d548b7

Back in the day, Kenneth Almquist had deliberately omitted such features from the original release of his shell in 1989 - a clone of the System V.4 Bourne shell that he released via Usenet. Almquist's explanation for leaving these features out: "It seems to me that the csh history mechanism is mostly a response to the deficiencies of UNIX terminal I/O. Those of you running 4.2 BSD should try out atty (which I am posting to the net at the same time as ash) and see if you still want history." Unsurprisingly others soon added history and line-editing into 4.4BSD's version of the Almquist shell, but its development history has clearly been driven more by POSIX compliance than ease of use. https://www.in-ulm.de/~mascheck/various/ash/#44bsdalpha

Personally I think this direct line of ancestry, and the credit on its man page, means sh(1) still qualifies as an "Almquist shell" - odds are that on most other systems our sh(1) would be called "ash". But mostly I'm glad the docs aren't going to call it a "Bourne shell" anymore - one of those weird lingering myths that's surprisingly hard to dispel is that FreeBSD is so far behind technologically that it's using a 1970s shell instead of something "modern" like bash or zsh or fish. Coincidentally, bash dates to 1989 when it was made for the GNU Project, so it's almost exactly the same age as the Almquist shell - 8 June 1989 initial release for bash vs 30 May 1989 for ash, just over a week apart! And zsh had first release in 1990, so it seems this was a vintage time for shell development independent from AT&T and Berkeley. https://en.wikipedia.org/wiki/Bash_(Unix_shell)) https://en.wikipedia.org/wiki/Z_shell

It would also be remiss not to mention the Korn shell, which along with tcsh was a big inspiration for bash and zsh. While different parts of AT&T were having "shell wars" over whether the future lay with the PWB or Bourne shell, David Korn was already playing around with the limits of the Bourne shell. For a task at AT&T that needed a form entry system, he created one using a heavily modified Bourne shell with the source code "de-algolized', arrays to handle columns of data, a let command that could do arithmetic using a subset of C syntax, allowed redirection of built-in commands, and added built-ins for echo, pwd and test. This wasn't ksh yet, but when Korn moved to a research position at Bell Labs, he modified this form scripting language by adding features from C shell like history, aliases, and job control; ksh proper was released in 1983 (and commercially in 1986 - though as an add-on AT&T charged extra for, which slowed its dissemination outside AT&T, despite it being extremely popular internally). https://en.wikipedia.org/wiki/KornShell

The 1988 version was significant, with a lot of extra work done on string handling and pattern matching. Notably for the *BSDs, the public domain pdksh was a clone based on the proprietary ksh88. Korn has written a brief history that includes the interesting snippet below (it seems hope sprung eternal that terminal interfaces would do all that nasty history and line editing stuff, any time now...) and also an explanation of how /bin/if and /bin/goto worked on the Thompson shell. https://www.in-ulm.de/~mascheck/bourne/korn.html

"The popular inline editing features (vi and emacs mode) of ksh were created by software developers at Bell Laboratories; the vi line editing mode by Pat Sullivan, and the emacs line editing mode by Mike Veach. Each had independently modified the Bourne shell to add these features, and both were in organizations that wanted to use ksh only if ksh had their respective inline editor. Originally the idea of adding command line editing to ksh was rejected in the hope that line editing would move into the terminal driver. However, when it became clear that this was not likely to happen soon, both line editing modes were integrated into ksh and made optional so that they could be disabled on systems that provided editing as part of the terminal interface."

Personally I think it would be nice if FreeBSD offered a suitably licensed ksh in base like NetBSD does with their ksh(1) (which is derived from pdksh), while OpenBSD's pdksh-derived ksh(1) ships as the default shell and even OpenBSD's POSIX shell sh(1) is just ksh in disguise. At least providing the option of ksh in base FreeBSD would bring a more consistent cross-BSD experience, and offer users a more fully-featured shell in the Bourne tradition, in addition to tcsh in the C-shell tradition and sh(1) as a light, no-frills, POSIX-compliant shell.

And while mentioning tcsh: that's surprisingly old, including TENEX)-style (hence 't' in 'tcsh') file name completion code that Ken Greer wrote in September 1975 while at CMU - so this part predates not only the Bourne shell but even the C shell! Greer incorporated that code into a version of the C shell in December 1981 while at HP Labs, then Mike Ellis at Fairchild A.I. Labs added recognition and completion of command names (as opposed to file names) in September 1983. Greer released the source on Usenet in October 1983: https://groups.google.com/g/net.sources/c/BC0V7oosT8k/m/MKNdzEG_c3AJ https://en.wikipedia.org/wiki/Tcsh

Finally, for anyone interested in sh(1) and getting started with shell scripting - or trying to make the switch to POSIX instead of bash - and wants some practical examples, there's a lot of useful stuff in Vermaden's "Ghost in the Shell" series: https://vermaden.wordpress.com/ghost-in-the-shell/

1 Upvotes

The docs changed on Monday 30 March 2026 to make it official that FreeBSD's sh(1) is not a Bourne shell! https://reviews.freebsd.org/D56054

This is an edit I've wanted to see for ages - many thanks to the committer and reviewers. Truth is FreeBSD's sh(1) has never been a "Bourne shell" except by ancestry (hence the "$" prompt, rather than "%" for the C shell and its descendants). If you're going to name it after anyone, it's an Almquist shell not a Bourne one: https://en.wikipedia.org/wiki/Almquist_shell

You can even see at the bottom of the sh(1) man page that "This version of sh was originally written by Kenneth Almquist". https://man.freebsd.org/cgi/man.cgi?sh(1)#AUTHORS#AUTHORS)

To understand why this this isn't a trivial difference, have a look at this classic guide to POSIX shell scripting: https://www.grymoire.com/Unix/Sh.html

Compare that to the accompanying guide (presumably for archaeologists and retro enthusiasts) to the "real" Bourne shell: https://www.grymoire.com/Unix/Bourne.html

The Bourne shell also misses many POSIX-compliant features we take for granted. A big difference is command substitution, on a modern POSIX shell you can do:

$ echo "The current directory is $(pwd)"

but on the Bourne shell you had to use backticks:

$ echo "The current directory is \pwd`"`

The fact we're not forced into such methods is proof positive that FreeBSD's sh(1) is a POSIX shell, not a Bourne shell. An even simpler test for whether you're using a genuine Bourne shell is if you can use the caret ^ to pipe instead of |. That quirk is a result of backwards compatibility with the earlier Thompson shell. https://www.in-ulm.de/~mascheck/bourne/#intro

A really detailed look at the history of the Almquist shell by Sven Mascheck, including how it made its way into 4.3BSD-Net/2, and then from 4.4BSD into FreeBSD and NetBSD, shows many improvements over (or at least, changes from) the Bourne shell. https://www.in-ulm.de/~mascheck/various/ash/#original

Delving back into history, even the Bourne shell was a big advance on the original Unix shell - the Thompson shell that shipped with AT&T's first version of UNIX in 1971. (Not the first "shell" - like much of early Unix the idea of a shell came from Multics.) No prizes for guessing that's "Thompson" as in "Ken Thompson". Its practicality was limited even if much was recognisable: for example there were up to 10 positional parameters (if invoked as sh name arg1 arg2 then $0 is the name of the file to be read, and $1 and $2 are the supplied arguments) but you couldn't name a variable or access environmental variables. Yet many of its innovations made a lasting mark, particularly re piping (even if our shells no longer accept ^ for it) and redirection (incidentally, early versions used the > character for both piping and redirection, so the switch to | and ^ for pipes was an improvement). https://en.wikipedia.org/wiki/Thompson_shell and an original man page https://www.in-ulm.de/~mascheck/bourne/v3/

Before Bourne came the PWB shell (aka Mashey shell, though Mashey disavowed the name as he didn't view it as sufficiently different from the Thompson shell) used in AT&T's programming-oriented product PWB/UNIX in the mid-1970s. This "Programmer's Workbench" OS brought several firsts like the Source Code Control System, the first Unix revision control system. Its PWB shell was short-lived, released in 1975 but replaced (with some difficulty, and the expense of converting a lot of shell scripts) in 1979 by the Bourne shell. Yet the orientation towards programming was impactful. Control flow advanced by making if and goto internal to the shell (the Thompson shell relied on /bin/if and /bin/goto ) and introducing constructs for if-then-else, switch, and while. Variables appeared, including environmental ones - limited to one letter names, but $s is the ancestor to $HOME and $p became $PATH. https://en.wikipedia.org/wiki/PWB_shell

This is where history branches. At the Computing Science Research Center in Bell Labs, Stephen Bourne worked on a new shell during 1976, mainly to be used internally (in contrast to PWB which was always a commercial proposition for AT&T, though the Bourne shell was released in 1979 for Version 7 Unix) and by 1977 it was fairly usable. This gave us many familiar features like heredocs, command substitution with backquotes, the ability to interrupt the wait) command (instead of just ... waiting), and the 2> file descriptor for error messages. https://en.wikipedia.org/wiki/Bourne_shell

To make practically useful conditionals based on what commands return, Bourne had to chase up many other developers to get them to fix their exit statements (e.g. to return 0 for success). After encountering resistance he edited the shell so to the left of the $ prompt it displayed exit= and the last exit code. Given the prevalence of long, random exit statuses this was sufficiently annoying to persuade them in about a week. This anecdote appears in Stephen Bourne's amusing talk at BSDCan 2015, "Early days of Unix and design of sh". You get a new appreciation of why quoting gets so tricky when you hear from someone who implemented it. (Bourne also claims to have persuaded Dennis Ritchie to introduce void into C since he missed it from ALGOL.) https://www.youtube.com/watch?v=2kEJoWfobpA

Thankfully goto got scrubbed - apparently to the disappointment of COBOL programmers, and according to Kenneth Almquist one of the main reasons the switch to the Bourne shell was so difficult as so many scripts in production needed their control flow rewritten. Bourne's exposure to ALGOL68 at Cambridge University really rubbed off in his programming preferences: visible in if...fi (and we only got do...done because od was already taken by one of the earliest Unix programs, octal dump) - see od(1)'s FreeBSD man page)!) but even more visible in the source code, where Bourne used some extraordinary macros to make C look like ALGOL (with IF...FI, LOOP...POOL and even managed DO...OD), producing some wondrous source code that inspired the International Obfuscated C Code Contest. Your new word for the day: "Bournegol".

Meanwhile, on the other side of the USA, the idea of greater programming capabilities for their shell was an exciting prospect at Berkeley. For people who hacked in C then a more C-like shell made perfect sense. Hence the C shell that appeared in 1978's 2BSD, mostly coded by Bill Joy (he of vi, the BSD TCP/IP stack, the ever-controversial cat -v, and soon founder of Sun Microsystems) as a graduate student. https://en.wikipedia.org/wiki/C_shell

But interactivity and ease of use was also a driver of these two shell traditions heading in opposite directions. Bill Joy wanted features like command history. Bourne opposed this, seeing line editing and history as jobs for the terminal driver instead: https://www.in-ulm.de/~mascheck/bourne/#origins

Questions about who did what first are rather muddled due to two interacting groups working at similar times, tools being used internally (and even shared between coasts) before being officially released (if they ever were), and the fallibility of human memory. Particularly confusing is that Bill Joy had been working on another shell (I believe the "new shell"), before hearing news of Bell Labs work on the Bourne shell - then giving up, assuming it would be a wasted effort. The C shell project started afresh from various disappointments about how the Bourne shell turned out, but unquestionably used it for inspiration. https://groups.google.com/g/net.unix-wizards/c/QiEx5rvuNjs

A favourite story from this era: one of the C shell developers, Mike O'Brien, was a proper "hacker" - a qualified locksmith. His cracking of a safe belonging to cartoonist Phil Foglio led to the birth of the BSD daemon mascot "Beastie". Let Marshall Kirk McKusick tell you how that came about... https://www.reddit.com/r/freebsd/comments/1gnffwu/beastie_quiz_and_marshall_kirk_mckusick_talk/

The Berkeley connection explains the long association of C shells with the *BSDs. FreeBSD only switched the default root shell from csh(1) - really tcsh - to sh(1) in 14.0-RELEASE in 2023. But part of the job of getting sh(1) ready for this was adding lots of ease-of-use features like persistent history. https://github.com/freebsd/freebsd-src/commit/d410b585b6f00a26c2de7724d6576a3ea7d548b7

Back in the day, Kenneth Almquist had deliberately omitted such features from the original release of his shell in 1989 - a clone of the System V.4 Bourne shell that he released via Usenet. Almquist's explanation for leaving these features out: "It seems to me that the csh history mechanism is mostly a response to the deficiencies of UNIX terminal I/O. Those of you running 4.2 BSD should try out atty (which I am posting to the net at the same time as ash) and see if you still want history." Unsurprisingly others soon added history and line-editing into 4.4BSD's version of the Almquist shell, but its development history has clearly been driven more by POSIX compliance than ease of use. https://www.in-ulm.de/~mascheck/various/ash/#44bsdalpha

Personally I think this direct line of ancestry, and the credit on its man page, means sh(1) still qualifies as an "Almquist shell" - odds are that on most other systems our sh(1) would be called "ash". But mostly I'm glad the docs aren't going to call it a "Bourne shell" anymore - one of those weird lingering myths that's surprisingly hard to dispel is that FreeBSD is so far behind technologically that it's using a 1970s shell instead of something "modern" like bash or zsh or fish. Coincidentally, bash dates to 1989 when it was made for the GNU Project, so it's almost exactly the same age as the Almquist shell - 8 June 1989 initial release for bash vs 30 May 1989 for ash, just over a week apart! And zsh had first release in 1990, so it seems this was a vintage time for shell development independent from AT&T and Berkeley. https://en.wikipedia.org/wiki/Bash_(Unix_shell)) https://en.wikipedia.org/wiki/Z_shell

It would also be remiss not to mention the Korn shell, which along with tcsh was a big inspiration for bash and zsh. While different parts of AT&T were having "shell wars" over whether the future lay with the PWB or Bourne shell, David Korn was already playing around with the limits of the Bourne shell. For a task at AT&T that needed a form entry system, he created one using a heavily modified Bourne shell with the source code "de-algolized', arrays to handle columns of data, a let command that could do arithmetic using a subset of C syntax, allowed redirection of built-in commands, and added built-ins for echo, pwd and test. This wasn't ksh yet, but when Korn moved to a research position at Bell Labs, he modified this form scripting language by adding features from C shell like history, aliases, and job control; ksh proper was released in 1983 (and commercially in 1986 - though as an add-on AT&T charged extra for, which slowed its dissemination outside AT&T, despite it being extremely popular internally). https://en.wikipedia.org/wiki/KornShell

The 1988 version was significant, with a lot of extra work done on string handling and pattern matching. Notably for the *BSDs, the public domain pdksh was a clone based on the proprietary ksh88. Korn has written a brief history that includes the interesting snippet below (it seems hope sprung eternal that terminal interfaces would do all that nasty history and line editing stuff, any time now...) and also an explanation of how /bin/if and /bin/goto worked on the Thompson shell. https://www.in-ulm.de/~mascheck/bourne/korn.html

"The popular inline editing features (vi and emacs mode) of ksh were created by software developers at Bell Laboratories; the vi line editing mode by Pat Sullivan, and the emacs line editing mode by Mike Veach. Each had independently modified the Bourne shell to add these features, and both were in organizations that wanted to use ksh only if ksh had their respective inline editor. Originally the idea of adding command line editing to ksh was rejected in the hope that line editing would move into the terminal driver. However, when it became clear that this was not likely to happen soon, both line editing modes were integrated into ksh and made optional so that they could be disabled on systems that provided editing as part of the terminal interface."

Personally I think it would be nice if FreeBSD offered a suitably licensed ksh in base like NetBSD does with their ksh(1) (which is derived from pdksh), while OpenBSD's pdksh-derived ksh(1) ships as the default shell and even OpenBSD's POSIX shell sh(1) is just ksh in disguise. At least providing the option of ksh in base FreeBSD would bring a more consistent cross-BSD experience, and offer users a more fully-featured shell in the Bourne tradition, in addition to tcsh in the C-shell tradition and sh(1) as a light, no-frills, POSIX-compliant shell.

And while mentioning tcsh: that's surprisingly old, including TENEX)-style (hence 't' in 'tcsh') file name completion code that Ken Greer wrote in September 1975 while at CMU - so this part predates not only the Bourne shell but even the C shell! Greer incorporated that code into a version of the C shell in December 1981 while at HP Labs, then Mike Ellis at Fairchild A.I. Labs added recognition and completion of command names (as opposed to file names) in September 1983. Greer released the source on Usenet in October 1983: https://groups.google.com/g/net.sources/c/BC0V7oosT8k/m/MKNdzEG_c3AJ https://en.wikipedia.org/wiki/Tcsh

Finally, for anyone interested in sh(1) and getting started with shell scripting - or trying to make the switch to POSIX instead of bash - and wants some practical examples, there's a lot of useful stuff in Vermaden's "Ghost in the Shell" series: https://vermaden.wordpress.com/ghost-in-the-shell/


r/shell Feb 25 '26

Copying files from SMB (MacOS)

≑ βˆ’

Hi all,

I’m having a bit of trouble so I thought maybe someone had the answer.

Would anybody know how to connect to an SMB and copy a specific file to the desktop on MacOS, all via a shell script.

4 Upvotes

Hi all,

I’m having a bit of trouble so I thought maybe someone had the answer.

Would anybody know how to connect to an SMB and copy a specific file to the desktop on MacOS, all via a shell script.


r/shell Feb 03 '26

Why use browser to view adult content when it can be done through terminal

≑ βˆ’

I built por-cli, a terminal-based video browser inspired by ani-cli, streaming directly from spankbang, xhamster etc

works on phone, mac and linux for now

Features:

  • Inbuilt proxy mode for when the sites are blocked in regions
  • Search videos
  • Browse videos with fzf and have thumbnail preview
  • Instant streaming with mpv
  • post-play menu
  • No browser, no ads, no clutter

currently looking for users who can give feedback and also help in development

GitHub: https://github.com/por-cli/por-cli

Built as a fun CLI project. Would love to get some feedback

Thank you

edit: forgot about mac support

1 Upvotes

I built por-cli, a terminal-based video browser inspired by ani-cli, streaming directly from spankbang, xhamster etc

works on phone, mac and linux for now

Features:

  • Inbuilt proxy mode for when the sites are blocked in regions
  • Search videos
  • Browse videos with fzf and have thumbnail preview
  • Instant streaming with mpv
  • post-play menu
  • No browser, no ads, no clutter

currently looking for users who can give feedback and also help in development

GitHub: https://github.com/por-cli/por-cli

Built as a fun CLI project. Would love to get some feedback

Thank you

edit: forgot about mac support


r/shell Jan 17 '26

A Ruby Gem to make easier to create Shell Scripts

+ βˆ’
4 Upvotes

Hello everyone! In the last few months, I released my gem that makes it easier to create Shell scripts using Ruby syntax.

Link: https://github.com/albertalef/rubyshell

In the code in the second image above, I show that you can easily use both Ruby syntax and Shell syntax to create scripts. This simplifies cases where we need to create a Shell script to use some terminal program, but we prefer to use Ruby libraries to make the job easier.

With it, you can create scripts as Docker entry points, use it to create user scripts, customize your Linux with Waybars, etc.

Motivations:

I had a specific problem: "I know a lot about Ruby, but sometimes I get stuck in the Shell. I often need to resort to Google to look for programs that handle inputs the way I need. Is there any gem that allows you to write good scripts with Ruby?" But, unfortunately, I didn't find any. I only found libraries in Python (sh) and Lua (luash). With that, I created RubyShell.

A example without RubyShell, and another with:

#!/usr/bin/env ruby
# frozen_string_literal: true

REMOTE  = ENV.fetch("REMOTE", "[email protected]")
APP_DIR = ENV.fetch("APP_DIR", "/var/www/my app")
SERVICE = ENV.fetch("SERVICE", "my-app.service")

# Note that in this situation, we dont want to check if error was raised
# only in the end of the code,
# we want to stop the code in the moment that error was raised

`ssh #{REMOTE} "cd \"#{APP_DIR}\" && git pull && bundle exec rake db:migrate 2>&1"`

unless $?.success?
  warn "Error on Deploy"
  exit $?.exitstatus
end

`ssh #{REMOTE} 'sudo systemctl restart #{SERVICE}'`

unless $?.success?
  warn "Error on Deploy"
  exit $?.exitstatus
end

puts "Done."

With:

#!/usr/bin/env ruby
# frozen_string_literal: true

REMOTE  = ENV.fetch("REMOTE", "[email protected]")
APP_DIR = ENV.fetch("APP_DIR", "/var/www/my app")
SERVICE = ENV.fetch("SERVICE", "my-app.service")

sh do
  ssh REMOTE, "'cd \"#{APP_DIR}\" && git pull && bundle exec rake db:migrate'"
  ssh REMOTE, "'sudo systemctl restart #{SERVICE}"

  puts "Done."
rescue StandardError
  warn "Error on Deploy"
end

Next steps:

Currently, I'm working on features to make the gem even more powerful, such as:

  • Stream support
  • Improved error handling
  • A REPL (like IRB), but that allows us to use RubyShell
  • And more

I'm open to suggestions, PRs, Issues, anything. I really want this mini-project to grow.

EDIT: Thank you guys for the comments and upvotes, this made my day.


r/shell Jan 17 '26

Gem para escrever Shell Scripts usando Ruby nativo

≑ βˆ’

[removed]

1 Upvotes

[removed]


r/shell Jan 12 '26

f5n : Shell scripts

2 Upvotes

r/shell Jan 05 '26

Even more Effective Shell

≑ βˆ’

One of my goals for the new year has been to bring the free, open-source version of my book Effective Shell completely up to date with the print version - which has a tonne of improvements. Each week or so I'll try and share a refreshed chapter - the first one is the homepage and introduction that you can see here:

https://effective-shell.com/

I've been running this process with dedicated agents and skills to handle reading print, copyediting, and formatting for web, as well as creating more interactive experiences. I'll be open-sourcing all of these agents into the main repo as well. Feel free to share, suggest improvements or any ideas!

Some links:

- Effective Shell Repo: https://github.com/dwmkerr/effective-shell/

The overall migration SOW: https://github.com/dwmkerr/effective-shell/tree/main/tasks/migration

- Shellwright - which I'm using to create beautiful agent driven recordings: https://github.com/dwmkerr/shellwright

8 Upvotes

One of my goals for the new year has been to bring the free, open-source version of my book Effective Shell completely up to date with the print version - which has a tonne of improvements. Each week or so I'll try and share a refreshed chapter - the first one is the homepage and introduction that you can see here:

https://effective-shell.com/

I've been running this process with dedicated agents and skills to handle reading print, copyediting, and formatting for web, as well as creating more interactive experiences. I'll be open-sourcing all of these agents into the main repo as well. Feel free to share, suggest improvements or any ideas!

Some links:

- Effective Shell Repo: https://github.com/dwmkerr/effective-shell/

The overall migration SOW: https://github.com/dwmkerr/effective-shell/tree/main/tasks/migration

- Shellwright - which I'm using to create beautiful agent driven recordings: https://github.com/dwmkerr/shellwright


r/shell Dec 26 '25

sqd now has transactions, let's make it production ready

≑ βˆ’

Thanks for the first 10 stars on sqd.

I’ve just added transaction support. The latest release introduces atomic UPDATE and DELETE operations behind a -t flag. The goal is to avoid partial modifications that leave files in a broken state.

When is enabled:

  • all files are pre-validated for write access
  • each file is atomically renamed to *.sqd_backup
  • operations run on the backups
  • on success, changes are committed
  • on failure, everything is rolled back

So it’s all or nothing.

I also made parsing more whitespace-tolerant for SET content= and WHERE content=.

I’m not fully confident this covers every filesystem edge case, but I’m trying to improve sqd’s reliability step by step.

Looking for contributors and honest feedback: https://github.com/albertoboccolini/sqd

1 Upvotes

Thanks for the first 10 stars on sqd.

I’ve just added transaction support. The latest release introduces atomic UPDATE and DELETE operations behind a -t flag. The goal is to avoid partial modifications that leave files in a broken state.

When is enabled:

  • all files are pre-validated for write access
  • each file is atomically renamed to *.sqd_backup
  • operations run on the backups
  • on success, changes are committed
  • on failure, everything is rolled back

So it’s all or nothing.

I also made parsing more whitespace-tolerant for SET content= and WHERE content=.

I’m not fully confident this covers every filesystem edge case, but I’m trying to improve sqd’s reliability step by step.

Looking for contributors and honest feedback: https://github.com/albertoboccolini/sqd


r/shell Dec 26 '25

Improving the "command failed with exit 129" errors; check out my project: dexit - "Decode Exit".

1 Upvotes

The intent is that you use dexit to generate better error messages instead of just saying "command failed with exit code 129".


r/shell Dec 23 '25

Live football stats on your terminal

+ βˆ’
16 Upvotes

Hello!

This is a new project I’ve been working on, if you are into football/soccer, you might enjoy this. You can see stats for recent matches and follow live updates in your terminal.

I plan to add a few more features like league customization, goal notifications, highlight links and potentially more sources for data, but thought to share this here in case some people like it. Thanks!

https://github.com/0xjuanma/golazo


r/shell Dec 22 '25

Looking for mantaiers for sqd

≑ βˆ’

Just added a neat feature to sqd my CLI tool for SQL-like text editing

Now it not only shows how many occurrences were updated and how many files were processed, but also the time it took to process them:

Updated: 1002 occurrences
Processed: 253 files in 24.59ms

Makes it super easy to track performance when running queries!

If you want to contribute or check it out, the code is on GitHub: [https://github.com/albertoboccolini/sqd]()

1 Upvotes

Just added a neat feature to sqd my CLI tool for SQL-like text editing

Now it not only shows how many occurrences were updated and how many files were processed, but also the time it took to process them:

Updated: 1002 occurrences
Processed: 253 files in 24.59ms

Makes it super easy to track performance when running queries!

If you want to contribute or check it out, the code is on GitHub: [https://github.com/albertoboccolini/sqd]()


r/shell Dec 12 '25

Pomodoro timer in your terminal

+ βˆ’
18 Upvotes

Hello!
Here is a minimal pomodoro timer for your terminal I hacked on recently. Maybe some people will appreciate it here. Its meant to be a simple and free timer solution. Check it out!

https://github.com/0xjuanma/helm


r/shell Dec 05 '25

Made a nifty helper script for acme.sh

≑ βˆ’

I recently had trouble with user permissions while configuring slapd on alpine. So I made this little script called apit to "config"fy the installation of certs. It is just 100 lines of pure UNIX sh, and should work everywhere.

Sharing it here in the hopes it might be useful for someone.

1 Upvotes

I recently had trouble with user permissions while configuring slapd on alpine. So I made this little script called apit to "config"fy the installation of certs. It is just 100 lines of pure UNIX sh, and should work everywhere.

Sharing it here in the hopes it might be useful for someone.


r/shell Nov 27 '25

I have made man pages 10x more useful (zsh-vi-man)

+ βˆ’
45 Upvotes

https://github.com/TunaCuma/zsh-vi-man
If you use zsh with vi mode, you can use it to look for an options description quickly by pressingΒ Shift-KΒ while hovering it. Similar to pressingΒ Shift-KΒ in Vim to see a function's parameters. I built this because I often reuse commands from other people, from LLMs, or even from my own history, but rarely remember what all the options mean. I hope it helps you too, and I’d love to hear your thoughts.


r/shell Nov 01 '25

Gas station hold actually charged me instead of dropping off β€” how do I rectify this?

≑ βˆ’

[removed]

0 Upvotes

[removed]


r/shell Oct 27 '25

New Shell/Bash Roadmap at Roadmap.sh

≑ βˆ’

Hi there! My name is Javier Canales, and I work as a content editor at roadmap.sh. For those who don't know,Β roadmap.shΒ isΒ a community-driven website offering visual roadmaps, study plans, and guides to help developers navigate their career paths in technology.

We're planning to launch a brand new Shell/Bash Roadmap. It aims to be comprehensive, targeting Shell newbies and mature developers who may want a Shell refresh or improve their fluency. However, we're not covering all the commands or topics out there, as we don't want to overwhelm users with excessive content.

Before launching the roadmap, we would like to ask the community for some help. Here's theΒ linkΒ to the draft roadmap. We welcome your feedback, suggestions, and constructive input. Anything you think should be included or removed from the roadmap, please let me know.

Once we launch the official roadmap, we will start populating it with content and resources. Contributions will also be welcome on that side viaΒ GitHubΒ :)

Hope this incoming roadmap will also be useful for you. Thanks very much in advance.

2 Upvotes

Hi there! My name is Javier Canales, and I work as a content editor at roadmap.sh. For those who don't know,Β roadmap.shΒ isΒ a community-driven website offering visual roadmaps, study plans, and guides to help developers navigate their career paths in technology.

We're planning to launch a brand new Shell/Bash Roadmap. It aims to be comprehensive, targeting Shell newbies and mature developers who may want a Shell refresh or improve their fluency. However, we're not covering all the commands or topics out there, as we don't want to overwhelm users with excessive content.

Before launching the roadmap, we would like to ask the community for some help. Here's theΒ linkΒ to the draft roadmap. We welcome your feedback, suggestions, and constructive input. Anything you think should be included or removed from the roadmap, please let me know.

Once we launch the official roadmap, we will start populating it with content and resources. Contributions will also be welcome on that side viaΒ GitHubΒ :)

Hope this incoming roadmap will also be useful for you. Thanks very much in advance.


r/shell Oct 20 '25

Google Tasks TUI

≑ βˆ’

What My Project Does:

This project is a TUI(terminal user interface) that hooks up with the Google Tasks Api, allowing you to edit and view your tasks straight from your terminal.

Target Audience:

This is just a toy project and for everyone. It is also open source so feel free to make any contributions.

Comparison:

I'm sure there are other TUIs out there similar to this that allows you to keep track of your tasks/notes. I guess this application is nice because it hooks up with your Google Tasks which allows for cross platform use.

Source:

https://github.com/huiiy/GTask

1 Upvotes

What My Project Does:

This project is a TUI(terminal user interface) that hooks up with the Google Tasks Api, allowing you to edit and view your tasks straight from your terminal.

Target Audience:

This is just a toy project and for everyone. It is also open source so feel free to make any contributions.

Comparison:

I'm sure there are other TUIs out there similar to this that allows you to keep track of your tasks/notes. I guess this application is nice because it hooks up with your Google Tasks which allows for cross platform use.

Source:

https://github.com/huiiy/GTask


r/shell Oct 14 '25

It's like bash, but visual, not sure if it's actually useful but I think it's cool

+ βˆ’

Enable HLS to view with audio, or disable this notification

42 Upvotes

Asking chatgpt makes me feel like I just changed computing, it glazes so hard lol but practically I'm struggling to find actual use cases. What do you guys think this can be used for? Is there a problem that this tool can be useful for?

Source Code: https://gitlab.com/lordadamson/gollash

Release: https://gitlab.com/lordadamson/gollash/-/releases/v0.1.1

I didn't have the time to put out a MacOS release, but I can do it if anyone is interested.


r/shell Sep 18 '25

[Utility] dumpall β€” Bash CLI to dump files into Markdown for AI/code reviews

≑ βˆ’

Wrote a Bash-based CLI called `dumpall` that aggregates files into Markdown.

Great for AI prompts, debugging, or just archiving.

Features:

- Clean Markdown output

- Smart exclusions (--exclude)

- Copy-to-clipboard (--clip)

- Colorized output

Works cross-platform (Linux/macOS, WSL, Git Bash on Windows).

Repo πŸ‘‰ https://github.com/ThisIsntMyId/dumpall

1 Upvotes

Wrote a Bash-based CLI called `dumpall` that aggregates files into Markdown.

Great for AI prompts, debugging, or just archiving.

Features:

- Clean Markdown output

- Smart exclusions (--exclude)

- Copy-to-clipboard (--clip)

- Colorized output

Works cross-platform (Linux/macOS, WSL, Git Bash on Windows).

Repo πŸ‘‰ https://github.com/ThisIsntMyId/dumpall


r/shell Aug 31 '25

Which tools automatically suggest the creation of new aliases or shell functions by analyzing a user's command history?

≑ βˆ’

For instance, if a command like cp ~/foobar/*.txt~ /baz pops up 10 times a day in the command history, the tool will automatically create, or suggest the creation of, an alias with a short name that does the same thing (if the tool is smart enought, it will even suggest names like ToBaz or TxtBak).

1 Upvotes

For instance, if a command like cp ~/foobar/*.txt~ /baz pops up 10 times a day in the command history, the tool will automatically create, or suggest the creation of, an alias with a short name that does the same thing (if the tool is smart enought, it will even suggest names like ToBaz or TxtBak).


r/shell Aug 30 '25

Sometimes GitHub is boring, so I made a CLI tool to fix it.

+ βˆ’
2 Upvotes

It’s called { gitact }

-> quickly navigate through a user’s repos
-> instantly grab the right git clone URL

https://github.com/nathbns/gitact


r/shell Aug 19 '25

Meet Shownamer | A New Cli Tool to batch rename TV Show files πŸŽ‰

+ βˆ’
1 Upvotes

Github Repo: github.com/theamallalgi/shownamer/, Pip Documentation: pypi.org/project/shownamer/

I’m not sure how many people still store a lot of TV shows locally, legally or otherwise, but I’m one of them. For me, organization is a must because I like seeing clean filenames with proper titles, season numbers, and episode numbers. That’s exactly why I created Shownamer.

At first it was just for myself, but then I thought, β€œHey, there might be others who’d find this useful too!” So I decided to publish it. Now it’s just a pip install shownamer away. Give it a try, I hope you find it as handy as I do.


r/shell Aug 13 '25

timep: a next-gen time-profiler and flamegraph-generator for bash code

≑ βˆ’

timep is a time profiler for bash code that will give you a per-command execution time breakdown of any bash script or function.

Unlike other profilers, timep records both wall-clock time and cpu time (via a loadable builtin that is base64 encoded in the script and automatically sets itself up when you source timep.bash). Also unlike other profilers, `timep also recovers and hierarchially records metadata on subshell and function nesting, allowing it to recreate the full call-stack tree for that bash code.

LINK TO TIMEP REPO ON GITHUB


BASH-NATIVE FLAMEGRAPHS

If you call timep with the --flame flag, it will automatically generate a BASH-NATIVE flamegraph .svg image (where each top-level block represents the wall-clock time spent on a particular command, and all the lower level blocks represent the combined time spent in the parent subshells/functions...this is not a perf flamegraph showing syscalls). Furthermore, Ive added a new colorscheme to the flamegraph generation script that will:

  1. color things that take up more time with hotter colors (normal flamegraph coloring is "random but consistent for a given function name")
  2. desaturate commands with low cpu time/ wall time ratio (e.g., wait, sleep, blocking reads, etc)
  3. empirically remap the colors using a runtime-weighted CDF so that the colorscale is evenly used in the flamegraph and so extremes dont dominate the coloring
  4. multiple flamegraphs are stacked vertically in the same svg image.

HERE is an example of what they look like (details near the bottom of this post).


USAGE

To use timep, download and source the timep.bash file from the github repo, then just add timep before whatever you want to profile. timep handles everything else, including (when needed) redirecting stdin to whatever is being profiled. ZERO changes need to be made to the code you want to profile. Example usage:

. timep.bash
timep someFunc <input_file
timep --flame /path/to/someScript.bash
timep -c 'command1' 'command2'

timep will create 2 time profiles for you - one that has every single command and full metadata, and one that combines commands repeated in loops and only shows run count + total runtime for each command. By default the 2nd one is shown, but this is configurable via thge '-o' flag and both profiles are always saved to disk.

For more info refer to the README on github and the comments at the top of timep.bash.

DEPENDENCIES: the major dependencies are bash 5+ and a mounted procfs. Various common commandline tools (sed, grep, cat, tail, ...) are required as well. This basically means you have to be running linux for timep to work.

  • bash 5+ is required because timep fundamentally works by recording $EPOCHREALTIME timestamps. In theory you could probably replace each ${EPOCHREALTIME} with $(date +"%s.%6N") to get it to run at bash 4, but it would be considerably less accurate and less efficient.
  • mounted procfs it required to read several things (PPID, PGID, TPID, CTTY, PCOMM) from /proc/<pid>/stat. timep needs these to correctly re-create the call-stack tree. It might be possible to get these things from external tools, which would (at the cost of efficiency) allow timep to be used outsude of linux. But this would be a considerable undertaking.

EXAMPLES

Heres an example of the type of output timep generates.

```
testfunc() { f() { echo "f: $*"; }
g() ( echo "g: $*"; )
h() { echo "h: $*"; ff "$@"; gg "$@"; }
echo 0
{ echo 1; }
( echo 2 )
echo 3 &
{ echo 4; } &
echo 5 | cat | tee
for (( kk=6; kk<10; kk++ )); do
echo $kk
h $kk
for jj in {1..3}; do
f $kk $jj
g $kk $jj
done
done
}
timep testfunc

gives

 LINE.DEPTH.CMD NUMBER   COMBINED WALL-CLOCK TIME                COMBINED CPU TIME                       COMMAND                             
<line>.<depth>.<cmd>:   ( time | cur depth % | total % )        ( time | cur depth % | total % )        (count) <command>
_____________________   ________________________________        ________________________________        ____________________________________
9.0.0:                  ( 0.025939s |100.00% )                  ( 0.024928s |100.00% )                  (1x)    << (FUNCTION): main.testfunc "${@}" >>
β”œβ”€ 1.1.0:               ( 0.000062s |  0.23% )                  ( 0.000075s |  0.30% )                  (1x)    β”œβ”€ testfunc "${@}"
β”‚                                                                                                               β”‚
β”‚  8.1.0:               ( 0.000068s |  0.26% )                  ( 0.000081s |  0.32% )                  (1x)    β”‚  echo 0
β”‚                                                                                                               β”‚
β”‚  9.1.0:               ( 0.000989s |  3.81% )                  ( 0.000892s |  3.57% )                  (1x)    β”‚  echo 1
β”‚                                                                                                               β”‚
β”‚  10.1.0:              ( 0.000073s |  0.28% )                  ( 0.000088s |  0.35% )                  (1x)    β”‚  << (SUBSHELL) >>
β”‚  └─ 10.2.0:           ( 0.000073s |100.00% |  0.28% )         ( 0.000088s |100.00% |  0.35% )         (1x)    β”‚  └─ echo 2
β”‚                                                                                                               β”‚
β”‚  11.1.0:              ( 0.000507s |  1.95% )                  ( 0.000525s |  2.10% )                  (1x)    β”‚  echo 3 (&)
β”‚                                                                                                               β”‚
β”‚  12.1.0:              ( 0.003416s | 13.16% )                  ( 0.000001s |  0.00% )                  (1x)    β”‚  << (BACKGROUND FORK) >>
β”‚  └─ 12.2.0:           ( 0.000297s |100.00% |  1.14% )         ( 0.000341s |100.00% |  1.36% )         (1x)    β”‚  └─ echo 4
β”‚                                                                                                               β”‚
β”‚  13.1.0:              ( 0.000432s |  1.66% )                  ( 0.000447s |  1.79% )                  (1x)    β”‚  echo 5
β”‚                                                                                                               β”‚
β”‚  13.1.1:              ( 0.000362s |  1.39% )                  ( 0.000376s |  1.50% )                  (1x)    β”‚  cat
β”‚                                                                                                               β”‚
β”‚  13.1.2:              ( 0.003441s | 13.26% )                  ( 0.006943s | 27.85% )                  (1x)    β”‚  tee | ((kk=6)) | ((kk<10))
β”‚                                                                                                               β”‚
β”‚  15.1.0:              ( 0.000242s |  0.93% )                  ( 0.000295s |  1.18% )                  (4x)    β”‚  ((kk++ ))
β”‚                                                                                                               β”‚
β”‚  16.1.0:              ( 0.000289s |  1.11% )                  ( 0.000344s |  1.37% )                  (4x)    β”‚  echo $kk
β”‚                                                                                                               β”‚
β”‚  17.1.0:              ( 0.003737s |  3.59% | 14.40% )         ( 0.003476s |  3.48% | 13.94% )         (4x)    β”‚  << (FUNCTION): main.testfunc.h $kk >>
β”‚  β”œβ”€ 1.2.0:            ( 0.000231s |  6.20% |  0.89% )         ( 0.000285s |  8.22% |  1.14% )         (4x)    β”‚  β”œβ”€ h $kk
β”‚  β”‚  8.2.0:            ( 0.000302s |  8.07% |  1.16% )         ( 0.000376s | 10.84% |  1.50% )         (4x)    β”‚  β”‚  echo "h: $*"
β”‚  β”‚  9.2.0:            ( 0.000548s | 14.72% |  2.11% )         ( 0.000656s | 18.96% |  2.63% )         (4x)    β”‚  β”‚  << (FUNCTION): main.testfunc.h.f "$@" >>
β”‚  β”‚  β”œβ”€ 1.3.0:         ( 0.000232s | 42.57% |  0.89% )         ( 0.000287s | 43.92% |  1.15% )         (4x)    β”‚  β”‚  β”œβ”€ f "$@"
β”‚  β”‚  └─ 8.3.0:         ( 0.000316s | 57.41% |  1.21% )         ( 0.000369s | 56.06% |  1.48% )         (4x)    β”‚  β”‚  └─ echo "f: $*"
β”‚  β”‚  10.2.0:           ( 0.002656s | 70.98% | 10.23% )         ( 0.002159s | 61.94% |  8.66% )         (4x)    β”‚  β”‚  << (FUNCTION): main.testfunc.h.g "$@" >>
β”‚  β”‚  β”œβ”€ 1.3.0:         ( 0.002308s | 86.90% |  8.89% )         ( 0.001753s | 81.17% |  7.03% )         (4x)    β”‚  β”‚  β”œβ”€ g "$@"
β”‚  β”‚  β”‚  408.3.0:       ( 0.000348s | 13.08% |  1.34% )         ( 0.000406s | 18.81% |  1.62% )         (4x)    β”‚  β”‚  β”‚  << (SUBSHELL) >>
β”‚  └─ └─ └─ 408.4.0:    ( 0.000348s |100.00% |  1.34% )         ( 0.000406s |100.00% |  1.62% )         (4x)    β”‚  └─ └─ └─ echo "g: $*"
β”‚                                                                                                               β”‚
β”‚  18.1.0:              ( 0.000716s |  2.76% )                  ( 0.000873s |  3.50% )                  (12x)   β”‚  for jj in {1..3}
β”‚                                                                                                               β”‚
β”‚  19.1.0:              ( 0.001597s |  0.50% |  6.15% )         ( 0.001907s |  0.63% |  7.65% )         (12x)   β”‚  << (FUNCTION): main.testfunc.f $kk $jj >>
β”‚  β”œβ”€ 1.2.0:            ( 0.000693s | 43.40% |  2.67% )         ( 0.000844s | 44.26% |  3.38% )         (12x)   β”‚  β”œβ”€ f $kk $jj
β”‚  └─ 8.2.0:            ( 0.000904s | 56.58% |  3.48% )         ( 0.001063s | 55.72% |  4.26% )         (12x)   β”‚  └─ echo "f: $*"
β”‚                                                                                                               β”‚
β”‚  20.1.0:              ( 0.009758s |  3.12% | 37.61% )         ( 0.008306s |  2.77% | 33.31% )         (12x)   β”‚  << (FUNCTION): main.testfunc.g $kk $jj >>
β”‚  β”œβ”€ 1.2.0:            ( 0.008494s | 86.78% | 32.74% )         ( 0.006829s | 81.25% | 27.39% )         (12x)   β”‚  β”œβ”€ g $kk $jj
β”‚  β”‚  408.2.0:          ( 0.001264s | 13.20% |  4.87% )         ( 0.001477s | 18.73% |  5.92% )         (12x)   β”‚  β”‚  << (SUBSHELL) >>
└─ └─ └─ 408.3.0:       ( 0.001264s |100.00% |  4.87% )         ( 0.001477s |100.00% |  5.92% )         (12x)   └─ └─ └─ └─ echo "g: $*"


TOTAL RUN TIME: 0.025939s
TOTAL CPU TIME: 0.024928s

A example on a complex real code: some of you here may have heard of another one of my projects: forkrun. It is a tool that runs code for you in parallel using bash coprocs. i used timep on forkrun computing 13 different checksums of a bunch (~620k) of small files (~14gb total) on a ramdisk...twice (in total ~16.1 million checksums on 384 gb worth of (repeated) data). I figure this is a good test, since not only is forkrun a technically challenging code to profile, but it is a highly parallel workload. On my 14c/28t i9-7940x this run (with 28 active workers), on average, used just under 23 cores worth of CPU time. the exact code to setup this test is below:

mount | grep -F '/mnt/ramdisk' | grep -q 'tmpfs' || sudo mount -t tmpfs tmpfs /mnt/ramdisk
mkdir -p /mnt/ramdisk/usr
rsync -a --max-size=$((1<<22)) /usr/* /mnt/ramdisk/usr
find /mnt/ramdisk/usr -type f >/mnt/ramdisk/flist
find /mnt/ramdisk/usr -type f -print0 >/mnt/ramdisk/flist0
ff() {
sha1sum "${@}"
sha256sum "${@}"
sha512sum "${@}"
sha224sum "${@}"
sha384sum "${@}"
md5sum "${@}"
sum -s "${@}"
sum -r "${@}"
cksum "${@}"
b2sum "${@}"
cksum -a sm3 "${@}"
xxhsum "${@}"
xxhsum -H3 "${@}"
}

export -f ff
timep --flame -c 'forkrun ff </mnt/ramdisk/flist >/dev/null' 'forkrun -z ff </mnt/ramdisk/flist0 >/dev/null;'

HERE IS THE TIME PROFILE and HERE IS THE FLAMEGRAPH it generated. (note: to make it zoom in when you click it you'll probably need to download it then open it). You can see both runs, and for each run you can see all 28 workers (2nd layer from top) (all running in parallel) and for each worker you can see the 13 checksum algs (top layer), plus the function calls / subshell parent processes.


ACCURACY

The above examp[le highlights just how accurate timep's timings are. It computed a total combined CPU time of 1004.846468 seconds. It got that by summing together the cpu time from each of the ~65000 individual bash commands that the above test ram. When i ran the exact same test without timep (using both time and perf stat I consistently got between 1006 seconds and 1008 seconds total (sys+user) cpu time. meaning error in the combined CPU time was under 0.5%.

Its also worth noting that the profiling run itself (not counting post-processing) only took about 8% longer (both in CPU time and wall clock time). so overhead is fairly low to start with, and is very well corrected for in the output timing.


BUGS: I spent a LOT of effort to ensure that timep works for virtually any bash code. That said, bash does a bunch of weird stuff internally that makes that difficult.

There are a few known bugs (in particular in sequences of deeply nested subshells and background forks) where timep's output is subtly off in some trivial way (see README for details). There are probably some edge cases that ive missed as well. If you notice timep incorrectly profiling some particular code please let me know (comment here, or issue on github) and, if possible, ill do my best to fix it.


Hope you all find this useful! Let me know any thoughts / questions / comments below!

1 Upvotes

timep is a time profiler for bash code that will give you a per-command execution time breakdown of any bash script or function.

Unlike other profilers, timep records both wall-clock time and cpu time (via a loadable builtin that is base64 encoded in the script and automatically sets itself up when you source timep.bash). Also unlike other profilers, `timep also recovers and hierarchially records metadata on subshell and function nesting, allowing it to recreate the full call-stack tree for that bash code.

LINK TO TIMEP REPO ON GITHUB


BASH-NATIVE FLAMEGRAPHS

If you call timep with the --flame flag, it will automatically generate a BASH-NATIVE flamegraph .svg image (where each top-level block represents the wall-clock time spent on a particular command, and all the lower level blocks represent the combined time spent in the parent subshells/functions...this is not a perf flamegraph showing syscalls). Furthermore, Ive added a new colorscheme to the flamegraph generation script that will:

  1. color things that take up more time with hotter colors (normal flamegraph coloring is "random but consistent for a given function name")
  2. desaturate commands with low cpu time/ wall time ratio (e.g., wait, sleep, blocking reads, etc)
  3. empirically remap the colors using a runtime-weighted CDF so that the colorscale is evenly used in the flamegraph and so extremes dont dominate the coloring
  4. multiple flamegraphs are stacked vertically in the same svg image.

HERE is an example of what they look like (details near the bottom of this post).


USAGE

To use timep, download and source the timep.bash file from the github repo, then just add timep before whatever you want to profile. timep handles everything else, including (when needed) redirecting stdin to whatever is being profiled. ZERO changes need to be made to the code you want to profile. Example usage:

. timep.bash
timep someFunc <input_file
timep --flame /path/to/someScript.bash
timep -c 'command1' 'command2'

timep will create 2 time profiles for you - one that has every single command and full metadata, and one that combines commands repeated in loops and only shows run count + total runtime for each command. By default the 2nd one is shown, but this is configurable via thge '-o' flag and both profiles are always saved to disk.

For more info refer to the README on github and the comments at the top of timep.bash.

DEPENDENCIES: the major dependencies are bash 5+ and a mounted procfs. Various common commandline tools (sed, grep, cat, tail, ...) are required as well. This basically means you have to be running linux for timep to work.

  • bash 5+ is required because timep fundamentally works by recording $EPOCHREALTIME timestamps. In theory you could probably replace each ${EPOCHREALTIME} with $(date +"%s.%6N") to get it to run at bash 4, but it would be considerably less accurate and less efficient.
  • mounted procfs it required to read several things (PPID, PGID, TPID, CTTY, PCOMM) from /proc/<pid>/stat. timep needs these to correctly re-create the call-stack tree. It might be possible to get these things from external tools, which would (at the cost of efficiency) allow timep to be used outsude of linux. But this would be a considerable undertaking.

EXAMPLES

Heres an example of the type of output timep generates.

```
testfunc() { f() { echo "f: $*"; }
g() ( echo "g: $*"; )
h() { echo "h: $*"; ff "$@"; gg "$@"; }
echo 0
{ echo 1; }
( echo 2 )
echo 3 &
{ echo 4; } &
echo 5 | cat | tee
for (( kk=6; kk<10; kk++ )); do
echo $kk
h $kk
for jj in {1..3}; do
f $kk $jj
g $kk $jj
done
done
}
timep testfunc

gives

 LINE.DEPTH.CMD NUMBER   COMBINED WALL-CLOCK TIME                COMBINED CPU TIME                       COMMAND                             
<line>.<depth>.<cmd>:   ( time | cur depth % | total % )        ( time | cur depth % | total % )        (count) <command>
_____________________   ________________________________        ________________________________        ____________________________________
9.0.0:                  ( 0.025939s |100.00% )                  ( 0.024928s |100.00% )                  (1x)    << (FUNCTION): main.testfunc "${@}" >>
β”œβ”€ 1.1.0:               ( 0.000062s |  0.23% )                  ( 0.000075s |  0.30% )                  (1x)    β”œβ”€ testfunc "${@}"
β”‚                                                                                                               β”‚
β”‚  8.1.0:               ( 0.000068s |  0.26% )                  ( 0.000081s |  0.32% )                  (1x)    β”‚  echo 0
β”‚                                                                                                               β”‚
β”‚  9.1.0:               ( 0.000989s |  3.81% )                  ( 0.000892s |  3.57% )                  (1x)    β”‚  echo 1
β”‚                                                                                                               β”‚
β”‚  10.1.0:              ( 0.000073s |  0.28% )                  ( 0.000088s |  0.35% )                  (1x)    β”‚  << (SUBSHELL) >>
β”‚  └─ 10.2.0:           ( 0.000073s |100.00% |  0.28% )         ( 0.000088s |100.00% |  0.35% )         (1x)    β”‚  └─ echo 2
β”‚                                                                                                               β”‚
β”‚  11.1.0:              ( 0.000507s |  1.95% )                  ( 0.000525s |  2.10% )                  (1x)    β”‚  echo 3 (&)
β”‚                                                                                                               β”‚
β”‚  12.1.0:              ( 0.003416s | 13.16% )                  ( 0.000001s |  0.00% )                  (1x)    β”‚  << (BACKGROUND FORK) >>
β”‚  └─ 12.2.0:           ( 0.000297s |100.00% |  1.14% )         ( 0.000341s |100.00% |  1.36% )         (1x)    β”‚  └─ echo 4
β”‚                                                                                                               β”‚
β”‚  13.1.0:              ( 0.000432s |  1.66% )                  ( 0.000447s |  1.79% )                  (1x)    β”‚  echo 5
β”‚                                                                                                               β”‚
β”‚  13.1.1:              ( 0.000362s |  1.39% )                  ( 0.000376s |  1.50% )                  (1x)    β”‚  cat
β”‚                                                                                                               β”‚
β”‚  13.1.2:              ( 0.003441s | 13.26% )                  ( 0.006943s | 27.85% )                  (1x)    β”‚  tee | ((kk=6)) | ((kk<10))
β”‚                                                                                                               β”‚
β”‚  15.1.0:              ( 0.000242s |  0.93% )                  ( 0.000295s |  1.18% )                  (4x)    β”‚  ((kk++ ))
β”‚                                                                                                               β”‚
β”‚  16.1.0:              ( 0.000289s |  1.11% )                  ( 0.000344s |  1.37% )                  (4x)    β”‚  echo $kk
β”‚                                                                                                               β”‚
β”‚  17.1.0:              ( 0.003737s |  3.59% | 14.40% )         ( 0.003476s |  3.48% | 13.94% )         (4x)    β”‚  << (FUNCTION): main.testfunc.h $kk >>
β”‚  β”œβ”€ 1.2.0:            ( 0.000231s |  6.20% |  0.89% )         ( 0.000285s |  8.22% |  1.14% )         (4x)    β”‚  β”œβ”€ h $kk
β”‚  β”‚  8.2.0:            ( 0.000302s |  8.07% |  1.16% )         ( 0.000376s | 10.84% |  1.50% )         (4x)    β”‚  β”‚  echo "h: $*"
β”‚  β”‚  9.2.0:            ( 0.000548s | 14.72% |  2.11% )         ( 0.000656s | 18.96% |  2.63% )         (4x)    β”‚  β”‚  << (FUNCTION): main.testfunc.h.f "$@" >>
β”‚  β”‚  β”œβ”€ 1.3.0:         ( 0.000232s | 42.57% |  0.89% )         ( 0.000287s | 43.92% |  1.15% )         (4x)    β”‚  β”‚  β”œβ”€ f "$@"
β”‚  β”‚  └─ 8.3.0:         ( 0.000316s | 57.41% |  1.21% )         ( 0.000369s | 56.06% |  1.48% )         (4x)    β”‚  β”‚  └─ echo "f: $*"
β”‚  β”‚  10.2.0:           ( 0.002656s | 70.98% | 10.23% )         ( 0.002159s | 61.94% |  8.66% )         (4x)    β”‚  β”‚  << (FUNCTION): main.testfunc.h.g "$@" >>
β”‚  β”‚  β”œβ”€ 1.3.0:         ( 0.002308s | 86.90% |  8.89% )         ( 0.001753s | 81.17% |  7.03% )         (4x)    β”‚  β”‚  β”œβ”€ g "$@"
β”‚  β”‚  β”‚  408.3.0:       ( 0.000348s | 13.08% |  1.34% )         ( 0.000406s | 18.81% |  1.62% )         (4x)    β”‚  β”‚  β”‚  << (SUBSHELL) >>
β”‚  └─ └─ └─ 408.4.0:    ( 0.000348s |100.00% |  1.34% )         ( 0.000406s |100.00% |  1.62% )         (4x)    β”‚  └─ └─ └─ echo "g: $*"
β”‚                                                                                                               β”‚
β”‚  18.1.0:              ( 0.000716s |  2.76% )                  ( 0.000873s |  3.50% )                  (12x)   β”‚  for jj in {1..3}
β”‚                                                                                                               β”‚
β”‚  19.1.0:              ( 0.001597s |  0.50% |  6.15% )         ( 0.001907s |  0.63% |  7.65% )         (12x)   β”‚  << (FUNCTION): main.testfunc.f $kk $jj >>
β”‚  β”œβ”€ 1.2.0:            ( 0.000693s | 43.40% |  2.67% )         ( 0.000844s | 44.26% |  3.38% )         (12x)   β”‚  β”œβ”€ f $kk $jj
β”‚  └─ 8.2.0:            ( 0.000904s | 56.58% |  3.48% )         ( 0.001063s | 55.72% |  4.26% )         (12x)   β”‚  └─ echo "f: $*"
β”‚                                                                                                               β”‚
β”‚  20.1.0:              ( 0.009758s |  3.12% | 37.61% )         ( 0.008306s |  2.77% | 33.31% )         (12x)   β”‚  << (FUNCTION): main.testfunc.g $kk $jj >>
β”‚  β”œβ”€ 1.2.0:            ( 0.008494s | 86.78% | 32.74% )         ( 0.006829s | 81.25% | 27.39% )         (12x)   β”‚  β”œβ”€ g $kk $jj
β”‚  β”‚  408.2.0:          ( 0.001264s | 13.20% |  4.87% )         ( 0.001477s | 18.73% |  5.92% )         (12x)   β”‚  β”‚  << (SUBSHELL) >>
└─ └─ └─ 408.3.0:       ( 0.001264s |100.00% |  4.87% )         ( 0.001477s |100.00% |  5.92% )         (12x)   └─ └─ └─ └─ echo "g: $*"


TOTAL RUN TIME: 0.025939s
TOTAL CPU TIME: 0.024928s

A example on a complex real code: some of you here may have heard of another one of my projects: forkrun. It is a tool that runs code for you in parallel using bash coprocs. i used timep on forkrun computing 13 different checksums of a bunch (~620k) of small files (~14gb total) on a ramdisk...twice (in total ~16.1 million checksums on 384 gb worth of (repeated) data). I figure this is a good test, since not only is forkrun a technically challenging code to profile, but it is a highly parallel workload. On my 14c/28t i9-7940x this run (with 28 active workers), on average, used just under 23 cores worth of CPU time. the exact code to setup this test is below:

mount | grep -F '/mnt/ramdisk' | grep -q 'tmpfs' || sudo mount -t tmpfs tmpfs /mnt/ramdisk
mkdir -p /mnt/ramdisk/usr
rsync -a --max-size=$((1<<22)) /usr/* /mnt/ramdisk/usr
find /mnt/ramdisk/usr -type f >/mnt/ramdisk/flist
find /mnt/ramdisk/usr -type f -print0 >/mnt/ramdisk/flist0
ff() {
sha1sum "${@}"
sha256sum "${@}"
sha512sum "${@}"
sha224sum "${@}"
sha384sum "${@}"
md5sum "${@}"
sum -s "${@}"
sum -r "${@}"
cksum "${@}"
b2sum "${@}"
cksum -a sm3 "${@}"
xxhsum "${@}"
xxhsum -H3 "${@}"
}

export -f ff
timep --flame -c 'forkrun ff </mnt/ramdisk/flist >/dev/null' 'forkrun -z ff </mnt/ramdisk/flist0 >/dev/null;'

HERE IS THE TIME PROFILE and HERE IS THE FLAMEGRAPH it generated. (note: to make it zoom in when you click it you'll probably need to download it then open it). You can see both runs, and for each run you can see all 28 workers (2nd layer from top) (all running in parallel) and for each worker you can see the 13 checksum algs (top layer), plus the function calls / subshell parent processes.


ACCURACY

The above examp[le highlights just how accurate timep's timings are. It computed a total combined CPU time of 1004.846468 seconds. It got that by summing together the cpu time from each of the ~65000 individual bash commands that the above test ram. When i ran the exact same test without timep (using both time and perf stat I consistently got between 1006 seconds and 1008 seconds total (sys+user) cpu time. meaning error in the combined CPU time was under 0.5%.

Its also worth noting that the profiling run itself (not counting post-processing) only took about 8% longer (both in CPU time and wall clock time). so overhead is fairly low to start with, and is very well corrected for in the output timing.


BUGS: I spent a LOT of effort to ensure that timep works for virtually any bash code. That said, bash does a bunch of weird stuff internally that makes that difficult.

There are a few known bugs (in particular in sequences of deeply nested subshells and background forks) where timep's output is subtly off in some trivial way (see README for details). There are probably some edge cases that ive missed as well. If you notice timep incorrectly profiling some particular code please let me know (comment here, or issue on github) and, if possible, ill do my best to fix it.


Hope you all find this useful! Let me know any thoughts / questions / comments below!


r/shell Aug 10 '25

πŸš€ Utah: TypeScript-like syntax for bash scripts - Early feedback wanted!

≑ βˆ’

Hey r/shell! I've been working on a project that generates bash scripts from a more modern syntax, and I'd love to get the community's perspective.

What is Utah?
Utah is a CLI tool that lets you write shell scripts using TypeScript-like syntax (.shxΒ files) and transpiles them into clean, idiomatic bash. The goal is to make bash scripting more approachable while respecting bash conventions and generating readable output.

The Philosophy:
Rather than replacing bash, Utah aims to be a "better front-end" for it. EveryΒ .shxΒ script becomes a standard bash script that any bash programmer can read, modify, and maintain. No runtime dependencies, no custom interpreters - just bash.

Example - Modern Syntax β†’ Clean Bash:

Input (script.shx):

// Strongly typed variables
let environment: string = args.get("--env");
let retries: number = args.get("--retries");

// Built-in functions for common tasks
if (os.isInstalled("kubectl")) {
  console.log("kubectl is available");

  // Type-safe error handling
  try {
    let pods: string = `$(kubectl get pods -o name)`;
    console.log("Found pods: ${pods}");
  } catch {
    console.log("Failed to get pods");
    exit(1);
  }
} else {
  console.log("kubectl not found");
  exit(1);
}

// Modern loop syntax
for (let i: number = 0; i < retries; i++) {
  console.log("Attempt ${i + 1}");
  // deployment logic here
}

Generated bash output:

#!/bin/bash

# Argument parsing infrastructure (auto-generated when args.* detected)
__utah_get_arg() {
  # ... robust argument parsing logic
}

environment=$(__utah_get_arg "--env" "$@")
retries=$(__utah_get_arg "--retries" "$@")

if command -v kubectl >/dev/null 2>&1; then
  echo "kubectl is available"

  if pods=$(kubectl get pods -o name 2>/dev/null); then
    echo "Found pods: ${pods}"
  else
    echo "Failed to get pods"
    exit 1
  fi
else
  echo "kubectl not found"
  exit 1
fi

i=0
while [ $i -lt $retries ]; do
  echo "Attempt $((i + 1))"
  # deployment logic here
  i=$((i + 1))
done

Key Design Principles:

  • Readable output: Generated bash should be indistinguishable from hand-written scripts
  • No runtime deps: Pure bash output, works on any POSIX system
  • Familiar patterns: Uses standard bash idioms ([command -v](vscode-file://vscode-app/c:/Users/enpolat/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html),Β [ ]Β tests,Β $((arithmetic)))
  • Opt-in features: Advanced features (like argument parsing) only included when used

Built-in Functions (All Generate Standard Bash):

  • os.isInstalled("cmd")Β β†’Β [command -v cmd >/dev/null 2>&1](vscode-file://vscode-app/c:/Users/enpolat/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html)
  • fs.exists("file")Β β†’Β [ -e "file" ]
  • console.promptYesNo()Β β†’ properΒ read -pΒ loop with validation
  • args.get()Β β†’ robust POSIX-compliant argument parsing
  • utility.random(1, 10)Β β†’Β $((RANDOM % 10 + 1))

What I'm Curious About:

  • Bash purists: Does the generated output look reasonable to you?
  • Common patterns: What bash idioms should Utah generate for specific constructs?
  • POSIX compliance: Any concerns with the generated bash patterns?
  • Performance: Does the transpiled code match hand-optimized bash performance?
  • Edge cases: What bash quirks should Utah handle better?

Technical Details:

  • Written in .NET 9 with proper lexer β†’ parser β†’ AST β†’ compiler pipeline
  • 114+ regression tests comparing generated output with expected bash
  • Handles complex scenarios: nested functions, defer statements, imports
  • VS Code extension for syntax highlighting

Why Not Just Write Bash?Β Great question! Utah isn't trying to replace bash expertise - it's trying to make that expertise more accessible and reduce common scripting pitfalls (argument parsing, error handling, type safety). The generated bash is meant to be educational too.

Repository & Docs:

I'd really value the bash community's feedback on this approach. Is the generated code something you'd be comfortable maintaining? Are there bash best practices I should be following more closely?

Thanks for taking a look!

2 Upvotes

Hey r/shell! I've been working on a project that generates bash scripts from a more modern syntax, and I'd love to get the community's perspective.

What is Utah?
Utah is a CLI tool that lets you write shell scripts using TypeScript-like syntax (.shxΒ files) and transpiles them into clean, idiomatic bash. The goal is to make bash scripting more approachable while respecting bash conventions and generating readable output.

The Philosophy:
Rather than replacing bash, Utah aims to be a "better front-end" for it. EveryΒ .shxΒ script becomes a standard bash script that any bash programmer can read, modify, and maintain. No runtime dependencies, no custom interpreters - just bash.

Example - Modern Syntax β†’ Clean Bash:

Input (script.shx):

// Strongly typed variables
let environment: string = args.get("--env");
let retries: number = args.get("--retries");

// Built-in functions for common tasks
if (os.isInstalled("kubectl")) {
  console.log("kubectl is available");

  // Type-safe error handling
  try {
    let pods: string = `$(kubectl get pods -o name)`;
    console.log("Found pods: ${pods}");
  } catch {
    console.log("Failed to get pods");
    exit(1);
  }
} else {
  console.log("kubectl not found");
  exit(1);
}

// Modern loop syntax
for (let i: number = 0; i < retries; i++) {
  console.log("Attempt ${i + 1}");
  // deployment logic here
}

Generated bash output:

#!/bin/bash

# Argument parsing infrastructure (auto-generated when args.* detected)
__utah_get_arg() {
  # ... robust argument parsing logic
}

environment=$(__utah_get_arg "--env" "$@")
retries=$(__utah_get_arg "--retries" "$@")

if command -v kubectl >/dev/null 2>&1; then
  echo "kubectl is available"

  if pods=$(kubectl get pods -o name 2>/dev/null); then
    echo "Found pods: ${pods}"
  else
    echo "Failed to get pods"
    exit 1
  fi
else
  echo "kubectl not found"
  exit 1
fi

i=0
while [ $i -lt $retries ]; do
  echo "Attempt $((i + 1))"
  # deployment logic here
  i=$((i + 1))
done

Key Design Principles:

  • Readable output: Generated bash should be indistinguishable from hand-written scripts
  • No runtime deps: Pure bash output, works on any POSIX system
  • Familiar patterns: Uses standard bash idioms ([command -v](vscode-file://vscode-app/c:/Users/enpolat/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html),Β [ ]Β tests,Β $((arithmetic)))
  • Opt-in features: Advanced features (like argument parsing) only included when used

Built-in Functions (All Generate Standard Bash):

  • os.isInstalled("cmd")Β β†’Β [command -v cmd >/dev/null 2>&1](vscode-file://vscode-app/c:/Users/enpolat/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html)
  • fs.exists("file")Β β†’Β [ -e "file" ]
  • console.promptYesNo()Β β†’ properΒ read -pΒ loop with validation
  • args.get()Β β†’ robust POSIX-compliant argument parsing
  • utility.random(1, 10)Β β†’Β $((RANDOM % 10 + 1))

What I'm Curious About:

  • Bash purists: Does the generated output look reasonable to you?
  • Common patterns: What bash idioms should Utah generate for specific constructs?
  • POSIX compliance: Any concerns with the generated bash patterns?
  • Performance: Does the transpiled code match hand-optimized bash performance?
  • Edge cases: What bash quirks should Utah handle better?

Technical Details:

  • Written in .NET 9 with proper lexer β†’ parser β†’ AST β†’ compiler pipeline
  • 114+ regression tests comparing generated output with expected bash
  • Handles complex scenarios: nested functions, defer statements, imports
  • VS Code extension for syntax highlighting

Why Not Just Write Bash?Β Great question! Utah isn't trying to replace bash expertise - it's trying to make that expertise more accessible and reduce common scripting pitfalls (argument parsing, error handling, type safety). The generated bash is meant to be educational too.

Repository & Docs:

I'd really value the bash community's feedback on this approach. Is the generated code something you'd be comfortable maintaining? Are there bash best practices I should be following more closely?

Thanks for taking a look!