PONYΞ»M2Modula-2
CodeCompared
for Bash programmers

You already know Bash.Now explore other languages.

Side-by-side, interactive cheatsheets for Bash programmers
comparing Bash to other languages. Every example runs live in your browser β€” no setup, no installation.

β–Ά Start with RubyBrowse comparisons ↓Explore the language map β†—

Choose your own path by reordering languages

Ruby⚑ Works Offline⚑ Offline

Your pipeline, back inside the language. Ruby's pitch to a shell programmer is specifically not Python's: a block plus Enumerable means fruit.tally.sort_by { … }.each { … } reads exactly the way sort | uniq -c | sort -rn reads β€” left to right, one stage at a time β€” except that every stage passes objects instead of text, so nothing is re-parsed and a count is a number you can add to.

  • The pipeline translates almost word for word: grep is select, grep -v is reject, awk '{print $2}' is map, uniq -c is tally, wc -l is count, head is first(n), xargs is each
  • 🚨 No word splitting. Every "$variable" you have typed was switching Bash's re-splitting off; there is nothing here to switch off, so a filename with a space stops being a hazard
  • A block is the idea with no shell counterpart β€” code passed to a method, which is why each, map and File.open are all one mechanism, and why cleanup lives in the library instead of in a trap
  • 🚨 Only nil and false are falsy. Zero is true and the empty string is true, which is neither the shell's exit-status convention nor Python's rule β€” so if value means "is there a value"
  • ruby -ane is a pipeline stage and ruby -i.backup -pe is sed -i, so the migration goes one stage at a time rather than all at once
  • Rake is the dispatch case block every mature script grows, with real dependencies that run once however many tasks name them
  • You still lose something: running programs is what a shell is for, and Open3.capture2 is three lines against one pipe. Move the logic, leave the glue
PythonBeta⚑ Works Offline⚑ Offline

For the script that has outgrown the shell. Not because shell scripts are bad β€” orchestration and glue are exactly what a shell is for β€” but because the moment a script grows a data structure, a decimal point, or a third option, Bash starts charging you for things Python gives away. The clearest single reason: word splitting simply ends. A string is one value everywhere, so a filename with a space in it stops being a hazard you quote your way around.

  • 🚨 No word splitting and no globbing of your data. Every "$variable" you have ever typed was you switching Bash's re-splitting off; in Python there is nothing to switch off
  • Real numbers. $(( )) is integer-only and anything with a decimal point means shelling out to bc and parsing text back β€” Python has integers of unlimited size, floats, decimal and fractions
  • Nested data. A dictionary of dictionaries is ordinary; Bash cannot express one, so scripts invent a flattened key and every encoding has a character it cannot survive
  • return returns a value β€” not an exit status between 0 and 255 β€” so a function costs no fork, keeps types, and can hand back a list
  • argparse replaces getopts and generates --help for you, long options included
  • json, csv and pathlib replace jq, cut and a pile of parameter expansions β€” and none of them can be broken by a quote in the data
  • You still lose something: a four-stage pipeline is shorter and streams better than anything Python will give you. Move the logic, and leave the glue where it is
PerlPre-Alpha

This is the migration Perl was built for. It is the language a generation of shell scripts were rewritten into, and the reason is narrow and concrete: regular expressions, hashes and file handling are in the language, so a four-stage grep | sed | sort | awk pipeline β€” four sub-languages with four quoting regimes running as four processes β€” collapses into one program with one set of rules.

  • Regular expressions are syntax, not an argument. =~ and s/// return their captures as a list you assign to named variables, instead of a global BASH_REMATCH array indexed by number
  • $count{$_}++ for @items; β€” the most-typed line in Perl's history, and the whole of sort | uniq -c | sort -rn
  • File tests are the same letters you already know β€” -e, -f, -d, -s, -r β€” and substr takes an offset and a length exactly as ${path:5:3} does
  • return returns a value, and a list comes back as a list β€” no printing it and forking a subshell to catch it
  • References are the one genuinely new idea, and they buy nested data: a hash of hashes, passed to a subroutine, which Bash cannot express at all
  • perl -lane is a pipeline stage, so you can migrate one gnarly stage at a time rather than rewriting the script in a day
  • 🚨 use v5.38; goes at the top of everything β€” it is set -euo pipefail, except that a misspelled variable is caught before the script starts rather than when the line is reached
ZshPre-Alpha⚑ Works Offline⚑ Offline

The shell macOS made the default, Zsh runs almost all of your Bash scripts unchanged β€” the short list of things that genuinely differ is worth an afternoon, and the parameter flags you get in exchange retire a pile of tr/sort/uniq pipelines.

  • Word splitting is off β€” an unquoted $variable stays one word, which retires the entire class of bugs caused by forgetting to quote
  • Arrays are 1-indexed and a bare $array means every element, not just ${array[0]}
  • Parameter flags β€” ${(j:,:)list}, ${(s:,:)text}, ${(u)list}, ${(on)numbers} β€” join, split, deduplicate and sort with no external tool and no subprocess
  • Glob qualifiers β€” *(.om[1]) is the newest regular file β€” replace most everyday find invocations
  • always blocks give the shell a real try/finally, scoped to a block instead of a single global EXIT trap
  • emulate -L sh restores Bash rules for one function, so a ported script can migrate a piece at a time
NushellPre-Alpha

The pipe you already know, carrying data instead of text. Nushell keeps | and throws out the parsing β€” ls | where size > 1mb | sort-by modified needs no awk, no cut, and no counting of whitespace-separated fields.

  • Columns have names, not numbers β€” get size instead of awk '{print $5}', and it cannot drift when the output format changes
  • No word splitting and no IFS β€” a value with a space in it is one value, in every context, with nothing to quote
  • sort, uniq --count, length, first, where are builtins over typed data, so the sort | uniq -c | sort -rn chain collapses to one command
  • from json makes jq unnecessary β€” JSON, YAML, TOML, CSV and XML all decode into the same records and tables
  • Real floating point, so no more shelling out to bc; and 1kb and 1min + 30sec are typed values, not strings
  • Errors are structured values with try/catch, replacing set -euo pipefail and the $? dance
Drag cards to reorder Β· your order is saved locally