blog · git · desktop · images · contact


„Copy and Paste“ für die Shell

2012-03-24

„Copy and Paste“ ist eigentlich eine schöne Sache. So per se ist das Konzept an der Shell nicht vorgesehen. Ich habe auch lange ohne es gelebt und hatte eigentlich keine Probleme, „obwohl“ ich nie einen Dateimanager an der Konsole benutzt habe. Habe einige ausprobiert, konnte mich damit aber nicht anfreunden.

Vor einiger Zeit bin ich dann im englischen Arch-Forum auf einen sehr einfachen Vorschlag gestoßen (ich finde leider das Original nicht mehr): Man kann das Konzept ja nachbauen, indem man einfach Shell-Funktionen wie diese hier definiert:

scut()
{
    mv -v "$@" ~/.drag
}

scopy()
{
    cp -av "$@" ~/.drag
}

spaste()
{
    [[ "$(ls -A ~/.drag)" ]] || { echo "Clipboard empty." >&2; return; }
    cp -avi ~/.drag/* .
}

sclear()
{
    rm -Rv ~/.drag/*
}

sls()
{
    ls -Ral ~/.drag
}

Das habe ich dann einige Wochen benutzt. Hat auch ganz passabel funktioniert. Besonders schön finde ich, dass es terminalübergreifend funktioniert: Ich kann in einem Terminal Dateien ausschneiden und sie in einem zweiten einfügen. Aber einige Nachteile gibt es:

Daher habe ich die Idee nun etwas anders umgesetzt. Ich lege jetzt nur noch eine Liste der Aktionen an, die durchgeführt werden sollen. Diese Liste wird in einer Textdatei gespeichert. „Aktion“ heißt: Es wird der jeweilige Dateiname und der gewünschte Vorgang („Cut“ oder „Copy“) hinterlegt. Das löst alle oben genannten Schwächen der originalen Version. Editieren kann ich die Liste dann ganz einfach mit einem Texteditor.

__scnp_rel2abs()
{
    # Übernommen von sgopherd.
    [[ -d "$1" ]] && { (cd -- "$1" 2>/dev/null; echo "$PWD"); return; }
    [[ -f "$1" ]] && { (cd -- "${1%/*}" 2>/dev/null; echo "$PWD/${1##*/}"); return; }
}

scut()
{
    for i
    do
        echo mv -vi
        __scnp_rel2abs "$i"
    done >> ~/.sdrag
}

scopy()
{
    for i
    do
        echo cp -avi
        __scnp_rel2abs "$i"
    done >> ~/.sdrag
}

spaste()
{
    while read -r action
    do
        read -r filename
        printf '%7s: ' "$action"
        $action "$filename" . < /dev/tty
    done < ~/.sdrag
}

sclear()
{
    : > ~/.sdrag
}

sedit()
{
    $EDITOR ~/.sdrag
}

sls()
{
    while read -r action
    do
        read -r filename
        printf '%7s: ' "$action"
        ls -ld "$filename"
    done < ~/.sdrag
}

Ein neuer Nachteil tut sich auf: Meine Version funktioniert nicht bei Dateinamen, die Newlines enthalten. Könnte man lösen, indem man in ~/.sdrag Null-Bytes als Trenner verwendet. Das ist keine große Änderung, aber ich war zu faul, das zu tun, weil soetwas bei mir nicht auftaucht. ;-)

– Nachtrag zum Wörtchen „faul“. Es ist so, dass Null-Bytes die Komplexität des meisten Codes nach oben treiben würden. Nicht viel, aber sie würden es. Insbesondere aber würde sedit deutlich komplexer werden. Im Moment ist das nur ein einfacher Aufruf des Editors. Aber mit Null-Bytes? Die kann ich nicht (oder nur schlecht) direkt im Editor lassen. Also muss ich die Liste von der Null-Byte-Darstellung in eine „lesbare“ Darstellung konvertieren, den Benutzer editieren lassen und dann zurückkonvertieren. Dabei muss berücksichtigt werden, dass in Dateinamen explizit alles außer einem Null-Byte erlaubt ist.

Hinkriegen würde man das schon. Die Frage, die sich dabei bloß stellt, ist: Lohnt es sich? Ich habe keine einzige Datei mit einem Newline im Namen auf der Platte. Daher bin ich „faul“ und lasse es bleiben, da es sich für mich nicht lohnt.

(Das ist keine Kritik an Kebas Kommentar unten, der „Einwand“ ist berechtigt. Ich wollte nur noch einmal deutlich zum Ausdruck bringen, weshalb ich das in meiner Version explizit nicht drin habe.)

– Nachtrag: Die coreutils 8.16 wurden eben released. Hier bringt ln jetzt die Option --relative mit. Das finde ich sehr wichtig und hierdurch wird eine Erweiterung der obigen Funktionen auf ln denkbar. Ich bräuchte nämlich möglichst einfach sowohl (symbolische) Links mit absolutem Ziel als auch Links mit relativem Ziel. Dennoch brauche ich die eher selten. Daher hatte ich auch diese Funktion weggelassen, weil sie nicht gerade trivial gewesen wäre. Mit einem nun vorhandenen --relative sieht das aber anders aus! Da geht noch was.

– Nachtrag: Die Funktion „spaste()“ wurde nochmal geändert. Es ist wichtig, dass „cp“ und „mv“ ein Terminal haben, von dem sie lesen können! Andernfalls können sie die Antwort für eine eventuelle „Wollen sie das überschreiben?“-Nachfrage nicht einlesen. Kurioserweise hatte ich bis jetzt (Ende April) keinen solchen Fall, bei dem etwas überschrieben worden wäre, deswegen hat es einen Monat gedauert, bis mir das aufgefallen ist.

– Nachtrag: Die Funktionen befinden sich mittlerweile als kleine Skripte im Git-Repo und wurden auch um die Funktion „__scnp_rel2abs()“ bereinigt:

stouch“ ist dafür gedacht, von der „.bashrc“ o.ä. aufgerufen zu werden.

Comments?