Problemumgehung für die Prozessersetzung in mksh

Ein äußerst nützliches Feature von Bash, bekannt als Process Substitution , fehlt in der Android-Shell, mksh . Dies ist sehr bedauerlich, da es Sie daran hindert, Dinge zu tun wie:

diff <(sort list1) <(sort list2)

Die mksh-Site hat dies hier als "Zukunftsplan" gekennzeichnet . Also meine Fragen sind:

Gibt es Workarounds dafür? (Und was sind sie?)

AFAIK steht es Ihnen frei, jede andere Shell zu verwenden, vorausgesetzt, sie ist mit Android kompatibel.
Es gibt jetzt ein mksh -Tag ;-)

Antworten (3)

Anscheinend ist die einzige (?) Möglichkeit, dies zu tun, die Verwendung einer benannten Pipe wie dieser:

mkfifo myp1 || exit
mkfifo myp2 || exit
sort list1 >myp1 &
sort list2 >myp2 &
diff myp1 myp2 
rm -f myp1 myp2

Dies muss in eine mksh- Shell-Funktion eingefügt werden, um von einer echten Befehlszeilenverwendung zu sein. Ein weiterer kniffliger Teil scheint zu sein, dass AOS eine Art Zeitüberschreitung implementiert hat, die die Pipe tötet oder durcheinander bringt, wenn sie nicht innerhalb weniger Sekunden verwendet wird. (Grund unbekannt.)

Wir haben gerade herausgefunden, wie das für den Desktop-Unix-Fall geht. Unter Android benötigen Sie ein Verzeichnis, in dem Sie temporäre FIFOs ablegen können (jedes reicht aus, z. B. /sqlite_stmt_journalin Android 2.x und /data/data(wenn Sie die Rechte haben, dort zu schreiben) in neueren). Sie benötigen außerdem mktempund mkfifo. ( catist heutzutage ein eingebautes mksh, aber auf altem Android müssen Sie diese oder eine neuere mksh-Version hinzufügen; sie funktionieren alle bis mindestens Android 1.5)

function die {
        print -ru2 -- "E: $*"
        exit 1
}

function psubin {
        local stdin=$(cat; echo .) pipe

        pipe=$(mktemp /tmp/psub.XXXXXXXXXX) || die mktemp

        # this is racy
        rm -f "$pipe"
        mkfifo "$pipe" || die mkfifo

        (
                # don’t block parent
                exec <&- >&- 2>&-
                # write content to FIFO
                print -nr -- "${stdin%.}" >"$pipe"
                # signal EOF to reader, ensure it’s not merged
                sleep 0.1
                :>"$pipe"
                # clean up
                (sleep 1; rm -f "$pipe") &
        ) &
        print -nr -- "$pipe"
}

diff -u $(sort list1 | psubin) $(sort list2 | psubin)

Sie benötigen eine Shell wie ksh, zsh oder bash für die Prozessersetzung.Ich gehe davon aus, dass der Diff-Befehl @ user1147688 von busybox stammt. Die Prozessersetzung funktioniert nicht mit Busybox-Apps.Busybox diff behandelt benannte Pipes anders als diffutils diff. Nach ein wenig mehr Tests stellte ich fest, dass ich die Prozesssubstitution nur mit busybox diff verwenden konnte, nachdem ich das /tmp-Verzeichnis mit diesem Supersu-Befehl erstellt hatte:

su -mm -d -c 'mount -t tmpfs -o rw,uid=0,gid=0,mode=1777 /tmp /tmp'

Dadurch wird eine temporäre Datei erstellt, die alle Benutzer verwenden können. Aus irgendeinem Grund verwendet busybox die TMPDIR-Variable nicht. Alternativ mit zsh können Sie busybox diff wie folgt verwenden:

busybox diff =(sort ./a) =(sort ./b)

Dies ist wie diff <(...) <(...), aber es verwendet temporäre Dateien anstelle von Named Pipes. Zsh verwendet jedes les- und beschreibbare temporäre Verzeichnis, das Sie TMPDIR zuweisen. Die Verwendung TMPDIR=/sdcardfunktioniert hier jedoch nicht, da Sie den Besitz oder die Berechtigungen für /sdcard-Dateien nicht ändern können.

Diffutils diff funktioniert ohne Probleme mit jeder Art von Prozesssubstitution.

Hier ist eine Funktion, die so etwas wie diff mit Prozesssubstitution implementiert. Sie können dies mit busybox diff und jeder modernen sh-kompatiblen Shell verwenden, die Arrays unterstützt, wie z. B. ksh, bash, zsh oder mksh. Es funktioniert, vorausgesetzt, HOME ist auf einen Ort mit Lese- und Schreibzugriff eingestellt, z. B. /sdcard auf Android.

# usage: diff2 COMMANDS1 -- COMMANDS2
diff2() {
        local i=1 j k cmd1 cmd2 list1 list2
        cmd1=()
        cmd2=()
        while (( i < $# )); do
                eval j="\$$i"
                if [[ $j = -- ]]; then
                        k=$i
                        break
                else
                        cmd1+=("$j")
                fi
                let i++
        done
        shift $k
        cmd2=("$@")
        list1=$HOME/diff$RANDOM
        list2=$HOME/diff$RANDOM
        eval "${cmd1[@]}" > $list1 2>&1
        eval "${cmd2[@]}" > $list2 2>&1
        diff $list1 $list2
        rm $list1 $list2
}

Sie können Pipes, |, zu Ihrem COMMAND1 oder COMMAND2 hinzufügen, solange Sie sie in Anführungszeichen setzen oder mit einem Backslash versehen.

Dies funktioniert, indem die Eingabe in zwei Arrays mit --als Trennzeichen für die Befehle aufgeteilt wird. Etwas evalMissbrauch hilft beim Trennen der Eingabe und wird benötigt, um Befehle auszuwerten, die Pipes verwenden.

Die Funktion könnte weiter modifiziert werden, um Optionen für diff einzuschließen, entweder unter Verwendung eines dritten Arrays oder durch Parsing mit getopts / GNU getopt. Ein drittes Array mit einem anderen --Trennzeichen würde wahrscheinlich am besten funktionieren, um zu vermeiden, GNU getopt für lange Optionen verwenden zu müssen.

Irgendetwas stimmt mit deinem Mount-Befehl nicht. Ich denke, es sollte sein: mount -t tmpfs -o rw,uid=0,gid=0,mode=1777 tmpfs /tmpund außerdem müssen Sie als RWmkdir -p /tmp neu einhängen und einhängen . Vielen Dank jedoch für die nützliche Antwort und das Skript. /