Schleife über eine Zeichenfolge in zsh und Bash

7

Ich möchte diese Bash-Schleife konvertieren:

x="one two three"

for i in ${x}
do
    echo ${i}
done

auf diese Weise mit Bash und zsh zu arbeiten

Diese Lösung funktioniert:

x=( one two three )

for i in ${x[@]}
do
    echo ${i}
done

Wie auch immer, ich ändere xvon einem String zu einem Array.

Gibt es eine Möglichkeit, $xin zsh eine Schleife zu erstellen, wenn es sich um eine Zeichenfolge handelt und mit Bash kompatibel ist?

Ich kenne zsh setopt shwordsplit, um Bash zu emulieren, aber ich kann es nicht ad hoc für die Schleife festlegen , da es in Bash nicht funktionieren würde.

`

Antonio
quelle

Antworten:

7
if type emulate >/dev/null 2>/dev/null; then emulate ksh; fi

In zsh werden dadurch Optionen aktiviert, die die Kompatibilität mit ksh und bash verbessern, einschließlich sh_word_split. In anderen Muscheln emulateexistiert nicht, also tut dies nichts.

Gilles 'SO - hör auf böse zu sein'
quelle
8

Gibt es eine Möglichkeit, $ x in zsh zu durchlaufen, wenn es sich um eine Zeichenfolge handelt und mit Bash kompatibel ist?

Ja!. Eine var-Erweiterung wird in zsh nicht (standardmäßig) aufgeteilt, Befehlserweiterungen jedoch . Daher können Sie sowohl in Bash als auch in zsh Folgendes verwenden:

 x="one two three"

 for i in $( echo "$x" )
 do
    echo "$i"
 done

In der Tat, der obige Code funktioniert in allen Bourne - Shell Nachkommen (aber nicht in der ursprünglichen Bourne, ändern $(…)zu , `…`um es dort arbeiten).

Der obige Code hat immer noch einige Probleme mit Globbing und der Verwendung von Echo. Lesen Sie weiter.



In zsh wird eine var-Erweiterung wie $varstandardmäßig nicht aufgeteilt (auch nicht glob).
Dieser Code hat keine Probleme (wird nicht auf alle Dateien im pwd erweitert) in zsh:

var="one * two"
printf "<%s> " ${var}; echo

Teilt var aber auch nicht durch den Wert von IFS.

Für zsh kann die Aufteilung auf IFS folgendermaßen erfolgen:

 1. Call splitting explicitly: `echo ${=var}`.
 2. Set SH_WORD_SPLIT option: `set -y; echo ${var}`.
 3. Using read to split to a list of vars (or an array with `-A`).

Aber keine dieser Optionen ist portabel für Bash (oder eine andere Shell außer ksh für -A).

Es readkönnte hilfreich sein , zu einer älteren Syntax zurückzukehren, die von beiden Shells gemeinsam genutzt wird .
Dies kann jedoch nur für Trennzeichen mit einem Zeichen (nicht IFS) und nur dann funktionieren , wenn das Trennzeichen in der Eingabezeichenfolge vorhanden ist:

 # ksh, zsh and bash(3.0+)
 t1(){  set -f;
        while read -rd "$delimiter" i; do
            echo "|$i|"
        done <<<"$x"
     }

Wo $1ist ein Trennzeichen für ein Zeichen ?

Das leidet immer noch unter der Erweiterung der Globbing-Zeichen ( *, ?und [), daher set -fist a erforderlich. outarrStattdessen können wir eine Array-Variable festlegen :

 # for either zsh or ksh
 t2(){ set -f; IFS="$delimiter" read -d $'\3' -A outarr < <(printf '%s\3' "$x"); }

Und die gleiche Idee für Bash:

 # bash
 t3(){ local -; set -f; mapfile -td "$1" outarr < <(printf '%s' "$x"); }

Der Effekt von set -fwird in der Bash-Funktion mithilfe von wiederhergestellt local -.

Das Konzept könnte sogar auf eine begrenzte Shell wie Dash erweitert werden:

 # valid for dash and bash
 t4(){  local -; set -f;
        while read -r i; do
             printf '%s' "$i" | tr "$delimiter"'\n' '\n'"$delimiter"; echo
        done <<-EOT
$(echo "$x" | tr "$delimiter"'\n' '\n'"$delimiter")
EOT
     } 

Nein <<<, nein <(…)und nein read -Aoder readarrayverwendet, aber es funktioniert (für ein Zeichenbegrenzer ) mit Leerzeichen, Zeilenumbrüchen und / oder Steuerzeichen in der Eingabe.

Aber es ist viel einfacher, einfach zu tun:

 t9(){ set -f; outarr=(   $(printf '%s' "$x")   ); }

Leider versteht zsh das nicht local -, daher muss der Wert von set -fwie folgt wiederhergestellt werden:

 t99(){ oldset=$(set +o); set -f; outarr=( $( printf '%s' "$x" ) ); eval "$oldset"; }

Jede der oben genannten Funktionen kann aufgerufen werden mit:

 IFS=$1 delimiter=$1 $2

Dabei ist das erste Argument $das Trennzeichen (und IFS) und das zweite Argument die aufzurufende Funktion (t1, t2,… t9, t99). Dieser Aufruf setzt den Wert von IFS nur für die Dauer des Funktionsaufrufs, der beim Beenden der aufgerufenen Funktion auf seinen ursprünglichen Wert zurückgesetzt wird.

Isaac
quelle
3

Wenn Sie keine Angst haben, eval (= böse) zu verwenden:

x="one two three"
eval "x=($x)"
for i in ${x[@]}; do 
    echo $i
done
Hielke Walinga
quelle
Die "einfachere" Lösung ist überhaupt nicht einfacher. im Gegenteil, es wird ein zusätzlicher Prozess (Subshell) in gegabelt bash. Der einzige Vorteil ist , dass es in der Lage sein wird , zu handhaben ;, |, (, usw. , aber nicht * in der xVariable. Ihre anfängliche Lösung (mit eval) war einfach besser.
Mosvy
Wie ist der (neue) erste Teil dieser Antwort besser als die Antwort, die ich bereits gepostet habe ?
Isaac
@mosvy (1) Die erste Antwort (als zweite Antwort) wird durch Globbing beeinflusst. Das ist leicht (richtig) zu vermeiden set -f. (2) Aber "die ursprüngliche Antwort" (mit Auswertung) wird auch von einem zweiten Expansionsschritt beeinflusst, der sehr schwer zu lösen ist (ich würde sagen: unmöglich zu lösen). (3) Ihnen fehlt der Punkt, dass der $(echo "$x")perfekt für alle Bourne wie Muscheln tragbar ist . (4) Wenn Sie Lösungen wünschen, die Eingaben einschließlich Leerzeichen, Zeilenumbrüchen und / oder Steuerzeichen korrekt verarbeiten, dann: Lesen Sie meine Antwort .
Isaac
@Isaac Ups, sorry Isaac, ich habe diese andere Methode tatsächlich realisiert, ohne deine Antwort zu lesen, die genau dieselbe Methode hat. Du musst mir in diesem Fall glauben. Wie auch immer, ich habe Ihre Antwort positiv bewertet und sie hat jetzt mehr positive Stimmen als ich, was ich für verdient halte. Rollback auch die Bearbeitung.
Hielke Walinga