This:
is an example of hashbang. It’s a very neat Unix concept: when placed at the beginning of a script, the line starting with #
(hash) and !
(bang) indicates an interpreter that should be chosen when running the script as an executable. Often used for shells (#!/bin/bash
, #!/bin/zsh
…), it also works for many regular programming languages, like Ruby, Python or Perl. Some of them may not even use #
as comment character but still allow for hashbangs, simply by ignoring such a first line. Funnily enough, this is just enough to fully “support” them, as the choice of interpreter is done at the system level.
Sadly, though, the only portable way to write a hashbang is to follow it with absolute path to an executable, which makes it problematic for pretty much anything other than /bin/*sh.
Take Python as an example. On many Linuxes it will be under /usr/bin/python, but that’s hardly a standard. What about /usr/local/bin/python? ~/bin/python?… Heck, one Python I use is under /usr/local/Cellar/python/2.7.3/bin – that’s installed by Homebrew on OS X, a perfectly valid Unix! And I haven’t even mentioned virtualenv…
This madness is typically solved by a standard tool called env, located under /usr/bin on anything at least somewhat *nixy:
env looks up the correct executable for its argument, relying on the PATH
environmental variable (hence its name). Thanks to env, we can solve all of the problems signaled above, and any similar woes for many other languages. That’s because by the very definition, running Python file with the above hashbang is equivalent to passing it directly to the interpreter:
Now, what if you wanted to also include some flags in interpreter invocation? For python
, for example, you can add -O
to turn on some basic optimizations. The seemingly obvious solution is to include them in hashbang:
Although this may very well work, it puts us again into “not really portable” land. Thankfully, there is a very ingenious (but, sadly, quite Python-specific) trick that lets us add arguments and be confident that our program will run pretty much anywhere.
Here’s how it looks like:
Understandably, it may not be immediately obvious how does it work. Let’s dismantle the pieces one by one, so we can see how do they all fit together – down not just to every quotation sign, but also to every space.
Many are the quirks of shell scripting. Most are related to confusing syntax, but some come from certain surprising semantics of Bash as a language, as well as the way scripts are executed.
Consider, for example, that you’d like to list files that are within certain size range. This is something you cannot do with ls alone. And while there’s certainly some awk incantation that makes it trivial, let’s assume you’re a rare kind of scripter who actually likes their hacks readable:
So you use an explicit while
loop, obtain the file size using stat and compare it to given bounds using a straightforward if
statement. Pretty simple code that shouldn’t cause any troubles later on… right?
But as your needs grow, you find that you also want to count how many files fall within your range, and how many do not. Given that you have an explicit if
, it appears like a simple addition (in quite literal sense):
Why it doesn’t work, then? Because clearly this is not the output we’re looking for (ls_between is our script here):
It seems that neither matches nor misses are counted properly, even though it’s clear from the printed list that everything is fine with our if
statement and loop. Wherein lies the problem?
In Unix-like systems, files and directories with names starting from ‘.’ (dot) are “hidden”: they don’t appear when listed by ls and are not shown by default in graphical file browsers. It doesn’t seem very clear why there is such a mechanism, especially when we have extensive chmod permissions and attributes which are not tied to filename. Actually, that’s one of distinguishing features of Unix/Linux: there are neither .exe nor .app files, just chmod +x.
But, here it is: name-based visibility control for files and directories. Why such a thing was ever implemented in the first place? Well, it turns out it was purely by accident:
Long ago, as the design of the Unix file system was being worked out, the entries . and .. appeared, to make navigation easier. (…) When one typed ls, however, these entries appeared, so either Ken [Thompson] or Dennis [Ritchie] added a simple test to the program. It was in assembler then, but the code in question was equivalent to something like this:
if (name[0] == '.') continue;
That test was meant to filter out . and .. only. Unintentionally, though, it ruled out a much bigger class of names: all that start with a dot, because that’s what it actually checked for. Back then it probably seemed like an innocuous detail. Fast-forward a couple of decades and it’s a de facto standard for storing program-specific data inside user’s home path. They can grow quite numerous over time, too:
That’s over 100 entries, a vast majority of my home directory. It’s neither elegant nor efficient to have that much of app-specific cruft inside the most important place in the filesystem. And even if GUI applications tend to collectively use a single ~/.config directory, the tradition to clutter the root $HOME path is strong enough to persist for foreseeable future.
Heed this as a warning. In the event your software becomes a basis for many derived solutions, future programmers will exploit every corner case of every piece of logic you have written. It doesn’t really matter what you wanted to put into your code, but only what you actually did.
Branża IT oraz nauka zwana informatyką liczą sobie już ładnych kilkadziesiąt i wygląda na to, że dziedzina ta zaczyna w pewnym sensie “dorastać”. W dość nieprzyjemnym sensie, muszę dodać. Jej znani pionierzy zaczynają bowiem nas opuszczać, czego dobitym dowodem jest zeszły miesiąc, gdy kilku z nich pożegnaliśmy w ciągu stosunkowo krótkiego czasu.
Ponieważ dzisiejszy dzień jest dobrą okazją do takich wspomnień, pozwolę sobie zwrócić uwagę na jednego z nich. Tego, który bez wątpienia miał największy wpływ na to, w jaki sposób programujemy komputery i jak obecnie wygląda przemysł software‘owy.
Mam tu na myśli oczywiście Dennisa Ritchie.
Wpływ Ritchiego na kształt informatyki jest trudny do przeceniania, bowiem miał on ogromny udział w początkach rozwoju dwóch jej ważnych aspektów: języków programowania i systemów operacyjnych. Jego zasługi w pierwszej z tych dziedzin wyrażają się głównie w stworzeniu języka C – prawdopodobnie najszerzej wykorzystywanego języka programowania w historii IT. Nawet jeśli sami nigdy w C nie programowaliśmy, to istnieje bardzo duża szansa, że nasz ulubiony język programowania ma z C wiele wspólnego: począwszy od bezpośredniej historii (C++, Objective-C), poprzez składnię (C#, Java, JavaScript, D, Scala, Go, itp.) aż po kluczowe narzędzia w rodzaju interpreterów napisanych w C (Python, Ruby, Perl, PHP). W rzeczywistości trudno jest wskazać język, który nie miałby czegokolwiek wspólnego z C – z ważniejszych należą do nich chyba tylko wczesne warianty Lispa (którego twórca, notabene, również zmarł w zeszłym miesiącu…). Niełatwo jest więc przesadzić, mówiąc o decydującym wpływie Ritchie’ego na kształt narzędzi używanych obecnie przez programistów na całym świecie.
Podobnie jest zresztą z oprogramowaniem w ogóle. System UNIX – którego Ritchie był jednym z kluczowych twórców – w niezliczonych odmianach i pochodnych działa na sporej części istniejących komputerów i wyrobów komputeropodobnych. Dotyczy to zarówno superkomputerów, wielkich serwerowni (żeby nie użyć słowa na ‘ch’ ;]) oraz małych serwerów, ale też domowych PC-tów i laptopów, a nawet urządzeń mobilnych: telefonów i tabletów. Większość (albo przynajmniej duża część) z nich operuje pod kontrolą systemów wywodzących się z UNIX-a i używa przynajmniej części związanego z nim stosu oprogramowania, którego prawdopodobnie najważniejszym komponentem jest… dokładnie tak – kompilator C.
Nie ma oczywiście żadnej pojedynczej osoby, której moglibyśmy zawdzięczać obecną postać technologii obliczeniowych i informacyjnych. Jednak Dennis Ritchie jest bez wątpienia człowiekiem, bez którego wyglądałaby ona dzisiaj zupełnie inaczej. Dlatego też warto o nim pamiętać – nawet jeśli wskaźniki z C czy uniksowy terminal są dla nas strasznymi rzeczami, z którymi nie chcemy mieć nic wspólnego :)