2017-11-26 3 views
2

Wenn Sie versuchen, alle Dateien in einem Verzeichnis zu durchlaufen, prüfen Sie sie auf das Vorhandensein einer Zeichenfolge und fügen Sie sie hinzu, wenn sie nicht existiert. Das ist, was ich habe:Warum bricht diese Schleife nach der ersten Iteration?

#!/bin/bash 
FILES=* 
for f in $FILES 
do 
    echo "Processing $f file..." 
    if grep -Fxq '<?xml version="1.0" encoding="UTF-8"?>' $f 
    then 
     continue 
    else 
     echo '<?xml version="1.0" encoding="UTF-8"?>' | cat - $f > temp && mv temp $f 
    fi 
done 

... aber das Skript stoppt nach der ersten Schleife. Irgendwelche Ideen warum?

+1

Es hängt davon ab, in welchem ​​Verzeichnis das Skript gestartet wird. Gibt es eine Datei? Mehr? – fernand0

+4

Verwenden Sie nicht 'FILES'; Es unterliegt der Parametererweiterung * und der Worttrennung, was zu Problemen führen kann, wenn ein beliebiger Dateiname Leerzeichen enthält. Verwenden Sie einfach 'für f in *'. – chepner

+0

Wo sollte die Zeichenfolge hinzugefügt werden? Ihre Frage ist ein bisschen knapp, weil sie die Anforderung nicht richtig erklärt. – sjsam

Antwort

2

Eine einfachere Lösung wäre, die Inplace bearbeiten Option wie -i des sed-Tool zu verwenden, unter

sed -i '1{/^<?xml version="1.0" encoding="UTF-8"?>/!{ 
s/^/<?xml version="1.0" encoding="UTF-8"?>\n/}}' /path/to/files/* 

Was wir oben

tun
  • Die Inplace-Option -i mit sed jeder macht Ändern Sie die Datei in die Datei geschrieben.
  • von 1{} wir verarbeiten nur die erste Zeile der Datei
  • Die /^<?xml version="1.0" encoding="UTF-8"?>/! prüft Teil, wenn die Zeichenfolge NICHT (beachten Sie das! Am Ende) in dem Anfang der Zeile.
  • Wenn die obige Bedingung wir den Anfang der Zeile (^) mit <?xml version="1.0" encoding="UTF-8"?>\n mit

     s/^/<?xml version="1.0" encoding="UTF-8"?>\n/ 
    
  • Der Rest schließt die geschweiften Klammern in der richtigen Reihenfolge nicht stimmt ersetzen :)

Das heißt, in Ihrem ursprünglichen Skript sehe ich Variablen wie FILES. Es wird davon abgeraten, Großbuchstaben als Benutzervariablen zu verwenden, da diese als Umgebungsvariablen reserviert sind und zu Konflikten führen können. Verwenden Sie stattdessen files.

Wieder

file=* 

die Implikation von [ word splitting ] tun hat und erzeugen unerwünschte Ergebnisse, wenn Sie Nicht-Standard-Dateien, die Räume oder sogar neue Linien enthalten. Was Sie tun können, ist

files=(*) # This put the files in an array 
for file in "${files[@]}" # Double quoting the array prevents word splitting 
do 
# Do something with "$file" but why bother when you've a one-liner with sed? ;-) 
done 

Hinweis: Für sed manuelle Besuch [ here ]

+1

Ein weit schlimmeres Problem mit 'FILES = *' ist die Worttrennung und Parametererweiterung ... – dawg

+0

@dawg Ich habe diesen Punkt übersehen .. Sehr wahr .. Was er hätte tun sollen, war 'files = (*)' und dann Werte lesen aus dem Array wie 'für die Datei in $ {files [@]}" ' – sjsam

+1

Es ist besser,' für f in in * 'zu tun, weil' (*) 'noch keine Dateinamen mit' \ n' in ihnen erlaubt. Selten, aber möglich. – dawg

1

ich, dass ich in den Kommentaren hier sah einige Dinge zu Wort Spaltung und den Dateinamen Erweiterung löschen möchten.

Bei Verwendung der Variablenzuweisung Bash Reference Manual werden nur die folgenden Erweiterungen vorgenommen: Tilde-Erweiterung, Parametererweiterung, Befehlsersetzung, arithmetische Erweiterung. Das bedeutet, dass in Ihrer Variablen $files wirklich nur ein Sternchen steht, da keine Dateinamenerweiterung stattfindet. An dieser Stelle müssen Sie sich also keine Gedanken über Zeilenumbrüche, Leerzeichen usw. machen, da sich in Ihrer Variablen keine Dateien befinden. Sie können dies mit declare -p files sehen.

Dies ist der Grund, warum Sie beim Zuweisen zu einer Variablen kein Zitat angeben müssen.

var=$othervariable 

ist die gleiche wie:

var="$othervariable" 

Nun, wenn Sie Ihre Variable $files in der for-Schleife verwenden for f in $files (beachten Sie, dass Sie nicht $files hier, weil der Dateiname Expansion würde nicht nehmen zitieren) Diese Variable wird erweitert und wird Worttrennung unterzogen. Aber der tatsächliche Wert ist JUST das Sternchen und Wortspaltung wird nichts mit dem Ergebnis tun! Zitiert die manual wieder: '?'

Nach Wort Spaltung, es sei denn, die Option -f eingestellt wurde (siehe Das Set Builtin), Bash prüft jedes Wort für die Zeichen '*', und '[' . Wenn eines dieser Zeichen angezeigt wird, wird das Wort als Muster angesehen und durch eine alphabetisch sortierte Liste von Dateinamen ersetzt, die dem Muster entsprechen (siehe Musterabgleich).

Bedeutung dafür ist, dass Dateinamen Expansion nach variable Expansion und Wort Spaltung erfolgt. Also werden die um die Dateinamenerweiterung erweiterten Dateien nicht von IFS geteilt! Daher funktioniert der folgende Code nur fein:

#!/usr/bin/env bash 

files=* 
for f in $files; do 
    echo "<<${f}>>" 
done 

und richtig ausgibt:

<<file with many  spaces>> 
<<filewith* weird characters[abc]>> 
<<normalfile>> 

Eine kürzere Version dieses offensichtlich for f in * anstelle der variablen $files zu verwenden ist. Sie möchten definitiv auch jede Verwendung von $f in Ihrer Schleife zitieren, da diese Erweiterung wirklich die Worttrennung durchläuft.


Dies gesagt, sollte Ihre Schleife ordnungsgemäß funktionieren.

Verwandte Themen