Was ist der Zweck der Verwendung von Shift in Shell-Skripten?

118

Ich bin auf dieses Skript gestoßen:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done

Welche Bedeutung haben die Zeilen, in denen sie verwendet werden shift? Ich nehme an, das Skript sollte zumindest mit Argumenten verwendet werden, also ...?

Patryk
quelle

Antworten:

141

shiftist eine bashintegrierte Funktion, mit der Argumente vom Anfang der Argumentliste entfernt werden. Da die drei Argumente an das Skript zur Verfügung gestellt in verfügbar sind $1, $2, $3, dann ein Anruf shift machen $2die neuen $1. A shift 2wird sich um zwei verschieben und aus Neuem $1Altes machen $3. Weitere Informationen finden Sie hier:

Mensch und Frieden
quelle
25
+1 Hinweis es nicht rotierenden sie ist es verschiebt sie von der Anordnung (Argumente). Shift / Unhift und Pop / Push sind gebräuchliche Bezeichnungen für diese allgemeine Art der Manipulation (siehe z. B. bashs pushdund popd).
Goldlöckchen
1
Die endgültige Referenz: gnu.org/software/bash/manual/bashref.html#index-shift
Glenn Jackman
@goldilocks sehr wahr. Anstelle von "drehen" hätte ich besser einen Weg finden sollen, ein anderes Wort zu verwenden, etwa "die Argumente in den Argumentvariablen vorwärts bewegen". Trotzdem hoffe ich, dass die Beispiele im Text und der Link zum Verweis es deutlich gemacht haben.
HumanityANDpeace
Obwohl das Skript erwähnt bash, ist die Frage für alle Shells allgemein, daher gilt der Posix-Standard: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa
32

Wie der Kommentar von Goldlöckchen und die Referenzen der Menschheit beschreiben, werden shiftdie Positionsparameter ( $1, $2usw.) neu zugewiesen , sodass $1der alte Wert von $2, $2der Wert von $3usw. übernommen wird. *   Der alte Wert von $1wird verworfen. ( $0wird nicht geändert.) Einige Gründe dafür sind:

  • Hiermit können Sie leichter auf das zehnte Argument zugreifen (sofern es eines gibt).  $10funktioniert nicht - es wird als $1verkettet mit einem interpretiert 0 (und könnte so etwas produzieren Hello0). Nach a shiftwird das zehnte Argument $9. (In den meisten modernen Shells können Sie jedoch verwenden ${10}.)
  • Wie der Bash-Leitfaden für Anfänger zeigt, kann er zum Durchlaufen der Argumente verwendet werden. IMNSHO, das ist ungeschickt; forist viel besser dafür.
  • Wie in Ihrem Beispielskript können Sie alle Argumente mit Ausnahme einiger weniger auf die gleiche Weise verarbeiten. Beispiel: In Ihrem Skript sind $1und $2Textzeichenfolgen, während $3und alle anderen Parameter Dateinamen sind.

So spielt es sich ab. Angenommen, Ihr Skript wird aufgerufen Patryk_scriptund heißt

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Das Skript sieht

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

Die Anweisung ostr="$1"setzt die Variable ostrauf USSR. Die erste shiftAnweisung ändert die Positionsparameter wie folgt:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

Die Anweisung nstr="$1"setzt die Variable nstrauf Russia. Die zweite shiftAnweisung ändert die Positionsparameter wie folgt:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

Und dann die forSchleife Änderungen USSR( $ostr) zu Russia( $nstr) in den Akten Treaty1, Atlas2und Pravda3.



Es gibt einige Probleme mit dem Skript.

  1. für Datei in $ @; machen

    Wenn das Skript als aufgerufen wird

    Patryk_script UdSSR Russland Vertrag1 "Weltatlas2" Pravda3

    es sieht

    $ 1 = UdSSR
    $ 2 = Russland
    $ 3 = Vertrag1
    $ 4 = Weltatlas2
    $ 5 = Pravda3

    aber, weil $@nicht zitiert wird, in dem Raum World Atlas2ist nicht in Anführungszeichen gesetzt, und die forSchleife denkt , dass es vier Dateien hat: Treaty1, World, Atlas2, und Pravda3. Dies sollte auch so sein

    für Datei in "$ @"; machen

    (um Sonderzeichen in den Argumenten zu zitieren) oder einfach

    für Datei zu tun

    (was der längeren Version entspricht).

  2. eval "sed 's /" $ ostr "/" $ nstr "/ g' $ file"

    Dies ist nicht unbedingt erforderlich eval, und die Weitergabe ungeprüfter Benutzereingaben an evalkann gefährlich sein. Zum Beispiel, wenn das Skript als aufgerufen wird

    Patryk_script "'; rm *;'" Russland-Vertrag1 Atlas2 Pravda3

    es wird ausgeführt rm *! Dies ist ein großes Problem, wenn das Skript mit höheren Berechtigungen ausgeführt werden kann als die des Benutzers, der es aufruft. Zum Beispiel, wenn es über sudoeine Weboberfläche ausgeführt oder von dieser aufgerufen werden kann. Es ist wahrscheinlich nicht so wichtig, wenn Sie es nur als Sie selbst in Ihrem Verzeichnis verwenden. Aber es kann geändert werden

    sed "s / $ ostr / $ nstr / g" "$ file"

    Dies birgt immer noch einige Risiken, die jedoch viel weniger schwerwiegend sind.

  3. if [ -f $file ], > $file.tmpUnd mv $file.tmp $file sollte es sein if [ -f "$file" ], > "$file.tmp"und mv "$file.tmp" "$file"jeweils mit Dateinamen umgehen , die Leerzeichen (oder andere lustige Zeichen) in ihnen haben könnte. (Mit dem eval "sed …Befehl werden auch Dateinamen mit Leerzeichen entstellt.)


* shift akzeptiert ein optionales Argument: eine positive Ganzzahl, die angibt, wie viele Parameter verschoben werden sollen. Der Standardwert ist eins ( 1). Zum Beispiel shift 4verursacht $5 zu werden $1, $6zu werden $2und so weiter. (Beachten Sie, dass das Beispiel im Bash-Handbuch für Anfänger falsch ist.) Daher kann Ihr Skript geändert werden

ostr="$1"
nstr="$2"
shift 2

was als klarer angesehen werden könnte.



Endnote / Warnung:

Die Windows-Eingabeaufforderungssprache (Batch-Datei) unterstützt auch einen SHIFTBefehl, der im Grunde das Gleiche wie der shiftBefehl in Unix-Shells tut , mit einem auffälligen Unterschied, den ich verstecke, um zu verhindern, dass die Benutzer dadurch verwirrt werden:

  • Ein Befehl wie SHIFT 4ist ein Fehler, der die Fehlermeldung "Ungültiger Parameter für SHIFT-Befehl" ausgibt.
  • SHIFT /n, wobei neine ganze Zahl zwischen 0 und 8 ist, ist gültig - aber es werden keine Zeiten verschoben . Es verschiebt sich einmal, beginnend mit dem n-  ten Argument. Dies bewirkt, dass (das fünfte Argument) zu wird und so weiter, wobei die Argumente 0 bis 3 allein bleiben.n SHIFT /4%5%4,  %6%5

Scott
quelle
2
shiftkann auf while, untilund sogar auf forSchleifen angewendet werden , um Arrays auf viel subtilere Weise zu handhaben als eine einfache forSchleife. Ich finde es oft nützlich in forLoops wie ... set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeserv
3

Die einfachste Erklärung ist dies. Betrachten Sie den Befehl:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

Ohne die Hilfe von können shiftSie nicht den vollständigen Wert des Standorts extrahieren, da dieser Wert beliebig lang werden kann. Wenn Sie zweimal verschieben, werden die Argumente SETund locationso entfernt, dass:

x="$@"
echo "location = $x"

Betrachte das $@Ding sehr lange . Dies bedeutet, dass der gesamte Ort xtrotz der Leerzeichen und Kommas in einer Variablen gespeichert werden kann. Zusammenfassend rufen wir also auf shiftund rufen später den Wert dessen ab, was von der Variablen übrig bleibt $@.

UPDATE Ich füge unten ein sehr kurzes Snippet hinzu, das das Konzept und den Nutzen von zeigt, shiftohne das es sehr, sehr schwierig wäre, die Felder korrekt zu extrahieren.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

Erläuterung : Nach dem shiftsoll die Variable b den Rest des zu übergebenden Materials enthalten, unabhängig von Leerzeichen oder Ähnlichem. Dies [ -n "$b" ] && echo $bist ein Schutz, so dass wir b nur drucken, wenn es einen Inhalt hat.

typelogisch
quelle
(1) Dies ist nicht die einfachste Erklärung. Es ist ein interessanter Winkel. (2) Solange Sie jedoch eine Reihe von Argumenten (oder alle) verketten möchten, möchten Sie sich vielleicht angewöhnen, "$*"statt zu verwenden "$@". (3) Ich wollte Ihre Antwort positiv bewerten, habe es aber nicht getan, weil Sie "zweimal verschieben" sagen, aber Sie zeigen sie nicht wirklich im Code an. (4) Wenn Sie möchten, dass ein Skript einen Wert mit mehreren Wörtern von der Befehlszeile übernimmt, ist es wahrscheinlich besser, den Benutzer aufzufordern, den gesamten Wert in Anführungszeichen zu setzen. … (Fortsetzung)
Scott
(Fortsetzung)… Es ist Ihnen heute vielleicht egal, aber Ihr Ansatz erlaubt es Ihnen nicht, mehrere Leerzeichen ( Philippines   6014), führende Leerzeichen, nachfolgende Leerzeichen oder Tabulatoren zu verwenden. (5) Übrigens können Sie möglicherweise auch das tun, was Sie hier tun, wenn Sie mit Bash arbeiten x="${*:3}".
Scott
Ich habe versucht zu entschlüsseln, woher Ihr Kommentar "*** erlaubt nicht mehrere Leerzeichen ***" kommt, da dieser nicht mit dem shiftBefehl zusammenhängt. Sie beziehen sich vielleicht auf subtile Unterschiede zwischen $ @ und $ *. Es bleibt jedoch die Tatsache bestehen, dass mein Snippet oben die Position genau extrahieren kann, unabhängig davon, wie viele Leerzeichen sie hat, entweder am Ende oder vorne, es spielt keine Rolle. Nach der Schicht enthält der Standort den korrekten Standort.
typelogic
2

shift Behandeln Sie Befehlszeilenargumente wie eine FIFO-Warteschlange. Bei jedem Aufruf wird ein Element nach links verschoben.

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
Algebra
quelle