blog · git · desktop · images · contact


Wo sind meine Dateien? Teil 2

2018-03-24

Die Sache mit „wie finde ich meine Dateien“ kam nochmal auf.

Damals dachte ich nur an „normale“ Programme. Kompilierte C-Programme. Das sieht aber alles schon ein bisschen anders aus, wenn man es mit interpretierten Skripten zu tun hat, die über den Shebang-Mechanismus gestartet werden.

Wie ging das mit Shebangs nochmal?

Man habe ein einfaches Shellskript:

#!/bin/sh

echo hi

Führt man es aus, …

$ ./my_script foo bar baz

… so wird der Kernel eigentlich die Binary „/bin/sh“ hinter den Kulissen ausführen. Das erste Argument wird der Pfad zu unserem Skript sein, danach kommen die an der Command-Line angegebenen Argumente. Eigentlich wird also das hier ausgeführt:

$ /bin/sh ./my_script foo bar baz

Und das ist auch schon das ganze Geheimnis. Führt man etwas mithilfe einer Shebang-Zeile aus, dann weiß der Interpreter sehr genau, wie der Pfad zum auszuführenden Skript lautet. Das ist eine Notwendigkeit, weil der Interpreter das Skript die Datei öffnen und lesen muss.

Es ist jetzt also nur noch eine Frage der Weitergabe. Wie kommt diese Information vom Interpreter zum eigentlichen Code?

GNU Bash

Die Bash hat eine besondere Variable dafür: „$BASH_SOURCE“. Angenommen also, man hat diese Verzeichnisstruktur:

opt/
└── mytool/
    ├── mytool*
    ├── some-resource.gif
    └── something-else.ogg

Und „/opt/mytool/mytool“ ist:

#!/bin/bash

my_dir=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")
ls -al "$my_dir"

Dann kann man so seine Dateien finden.

Dieser Ansatz ist auch nicht neu und wurde zum Beispiel hier schon skizziert:

Einen eigenen Shebang-„Interpreter“ schreiben

Interpreter, die in Shebang-Zeilen erwähnt werden, sind keine besonderen Programmen. Das müssen auch keine tatsächlichen Interpreter sein und man kann da dementsprechend auch leicht etwas eigenes benutzen.

Erstellen wir eine ganz einfache, ausführbare Datei mit nur einer Zeile Inhalt:

#!/opt/mytool/mytool-shebang

Das Ding nennen wir „mytool“ und platzieren es wieder in „/opt/mytool“.

Will man das nun ausführen, so wird das Betriebssystem die Shebang-Zeile auswerten und dann versuchen, „/opt/mytool/mytool-shebang“ zu starten. Erstellen wir diese Datei also. Das kann ein weiteres Skript sein oder etwas anderes wie zum Beispiel ein C-Programm – nehmen wir mal letzteres.

#define _XOPEN_SOURCE 500

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char **argv)
{
    int i;
    char *rp;

    if (argc < 2)
    {
        fprintf(stderr, "Need argv[1]\n");
        return 1;
    }

    rp = realpath(argv[1], NULL);
    printf("arg 1: '%s', resolved: '%s'\n", argv[1], rp);
    free(rp);

    for (i = 2; i < argc; i++)
        printf("arg %d: '%s'\n", i, argv[i]);

    return 0;
}

Übersetzen:

$ cc -std=c99 -Wall -Wextra -o mytool-shebang mytool-shebang.c

Und das war’s im Wesentlichen schon. Man kann jetzt „mytool“ auf verschiedene Weisen aufrufen und es wird den richtigen Pfad finden:

$ cd /opt/mytool
$ ./mytool
arg 1: './mytool', resolved: '/opt/mytool/mytool'

$ ./mytool foo bar
arg 1: './mytool', resolved: '/opt/mytool/mytool'
arg 2: 'foo'
arg 3: 'bar'

$ cd /tmp
$ /opt/mytool/mytool
arg 1: '/opt/mytool/mytool', resolved: '/opt/mytool/mytool'

$ cd /tmp
$ PATH=$PATH:/opt/mytool mytool
arg 1: '/opt/mytool/mytool', resolved: '/opt/mytool/mytool'

Dass „realpath()“ benutzt wird, sorgt dann auch dafür, dass Symlinks kein Problem sind:

$ cd /tmp
$ ln -s /opt/mytool/mytool
$ ./mytool 
arg 1: './mytool', resolved: '/opt/mytool/mytool'

$ cd /tmp
$ rm mytool 
$ ln -s /opt/mytool 
$ cd mytool
$ ./mytool
arg 1: './mytool', resolved: '/opt/mytool/mytool'

Tweak: Alles in den „$PATH“ tun

/opt/mytool/mytool“ sieht derzeit so aus:

#!/opt/mytool/mytool-shebang

Man kann nun das tun, was viele Python-Skripte tun, und „/usr/bin/env“ dazu missbrauchen, den Pfad für einen abzuklappern:

#!/usr/bin/env mytool-shebang

Und dann geht das:

$ PATH=$PATH:/opt/mytool mytool
arg 1: '/opt/mytool/mytool', resolved: '/opt/mytool/mytool'

Fazit

Das alles sollte relativ portabel sein, ausprobiert habe ich es aber nur unter Linux und OpenBSD.

Ich weiß nicht, ob ich einen Hack dieser Art tatsächlich in einem Projekt mal einsetzen würde, weil es sich schon ein bisschen umständlich anfühlt. Untergekommen ist mir das so auch noch nicht. Es ist aber nett, zu wissen, welche Optionen man so hat. :-)

Comments?