2016-08-09 136 views
0

bei der Verwendung von sed -e, einige Parameter einer Konfigurationsdatei aktualisieren und es an | tee (um den aktualisierten Inhalt in die Datei zu schreiben), dies bricht zufällig und verursacht die Datei zu sein ungültig (Größe 0).Aktualisieren einer Datei mit Tee zufällig fehlschlägt in Linux Bash-Skript

Zusammengefasst wird dieser Code für die Aktualisierung Parameter verwendet:

# based on the provided linenumber, add some comments, add the new value, delete old line 

sed -e "$lineNr a # comments" -e "$lineNr a $newValue" -e "$lineNr d" $myFile | sudo tee $myFile 

ich ein Skript einrichten, die dieses Update-Befehl 100 Mal aufruft.

  • In einer Ubuntu VM (Parallels Desktop) auf einem gemeinsamen Verzeichnis mit OSX dies geschieht Verhalten
  • In einer Ubuntu VM (Parallels Desktop) auf der Ubuntu-Partition dieses Verhalten bis zu 40 bis 50-fache bis auftritt mal
  • Auf einem nativen System (IntelNUC mit Ubuntu) tritt dieses Verhalten

bis 15-mal nach oben Kann jemand erklären, warum dies geschieht?

Hier ist ein voll funktionsfähiges Skript, wo Sie das Experiment auch ausführen können. (Alle erforderlichen Dateien werden vom Skript generiert, so können Sie einfach kopieren/sie in eine bashscriptfile einfügen und ausführen)

#!/bin/bash 
# main function at bottom 

#==================== 
#===HELPER METHOD==== 
#==================== 

# This method updates parameters with a new value. The replacement is performed linewise. 
doUpdateParameterInFile() 
{ 
    local valueOfInterest="$1" 
    local newValue="$2" 
    local filePath="$3" 

    # stores all matching linenumbers 
    local listOfLines="" 
    # stores the linenumber which is going to be replaced 
    local lineToReplace="" 

    # find value of interest in all non-commented lines and store related lineNumber 
    lineToReplace=$(grep -nr "^[^#]*$valueOfInterest" $filePath | sed -n 's/^\([0-9]*\)[:].*/\1/p') 

    # Update parameters 
    # replace the matching line with the desired value 
    oldValue=$(sed -n "$lineToReplace p" $filePath) 
    sed -e "$lineToReplace a # $(date '+%Y-%m-%d %H:%M:%S'): replaced: $oldValue with: $newValue" -e "$lineToReplace a $newValue" -e "$lineToReplace d" $filePath | sudo tee $filePath >/dev/null 

    # Sanity check to make sure file did not get corrupted by updating parameters 
    if [[ ! -s $filePath ]] ; then 
    echo "[ERROR]: While updating file it turned invalid." 
    return 31 
    fi 

} 

#=============================== 
#=== Actual Update Function ==== 
#=============================== 

main_script() 
{ 
    echo -n "Update Parameter1 ..." 
    doUpdateParameterInFile "Parameter1" "Parameter1 YES" "config.txt" 
    if [[ "$?" == "0" ]] ; then echo "[ OK ]" ; else echo "[FAIL]"; return 33 ; fi 

    echo -n "Update Parameter2 ..." 
    doUpdateParameterInFile "Parameter2" "Parameter2=90" "config.txt" 
    if [[ "$?" == "0" ]] ; then echo "[ OK ]" ; else echo "[FAIL]"; return 34 ; fi 

    echo -n "Update Parameter3 ..." 
    doUpdateParameterInFile "Parameter3" "Parameter3 YES" "config.txt" 
    if [[ "$?" == "0" ]] ; then echo "[ OK ]" ; else echo "[FAIL]"; return 35 ; fi 
} 

#================= 
#=== Main Loop === 
#================= 

#generate file config.txt 
printf "# Configfile with 3 Parameters\n#[Parameter1]\n#only takes YES or NO\nParameter1 NO \n\n#[Parameter2]\n#Parameter2 takes numbers\nParameter2 = 100 \n\n#[Parameter3]\n#Parameter3 takes YES or NO \nParameter3 YES\n" > config.txt 
cp config.txt config.txt.bkup 

# Start the experiment and let it run 100 times 
cnt=0 
failSum=0 
while [[ $cnt != "100" ]] ; do 
    echo "==========run: $cnt; fails: $failSum=======" 
    main_script 
    if [[ $? != "0" ]] ; then cp config.txt.bkup config.txt ; failSum=$(($failSum+1)) ; fi 
    cnt=$((cnt+1)) 
    sleep 0.5 
done 

Grüße DonPromillo

+2

Warum Sie diese 'tee' verwenden, wenn Sie' stdout' sind wegwerfen? Es sieht auch so aus, als ob Sie eine Race Condition am wahrscheinlichsten haben, da Sie "tee" verwenden, um die Datei genau zur gleichen Zeit zu überschreiben, wie mit "sed", um sie zu verarbeiten. Wenn 'tee' die Datei abschneidet, bevor' sed' es bekommen kann, erhalten Sie eine '0' Datei. Wenn Ihr 'sed' es unterstützt, können Sie die Datei an Ort und Stelle ändern, andernfalls sollten Sie die Ausgabe in eine temporäre Datei schreiben und dann den ursprünglichen Namen verwenden. –

+1

Die beiden Seiten einer Pipe sind asynchron; Sie können nicht garantieren, dass "sed" den Inhalt von "myFile" vollständig verbraucht, bevor "tee" es überschreibt. – chepner

+0

Danke @EricRenouf und @chepner für den Hinweis ganz klar. Der Grund, warum ich "Tee" benutze, ist im Grunde Neugier in Rohrleitungen. – DonPromillo

Antwort

3

Das Problem ist, dass Sie verwenden tee zu überschreiben $filepath zur gleichen Zeit wie sed versucht, daraus zu lesen. Wenn tee es zuerst abschneidet, dann erhält sed eine leere Datei und Sie enden mit einer 0 Längen-Datei am anderen Ende.

Wenn Sie GNU haben sed Sie können die -i Flag verwenden, haben sed die Datei anstelle ändern (andere Versionen unterstützen -i erfordern aber ein Argument, um es). Wenn Ihr sed es nicht unterstützen, können Sie es auf eine temporäre Datei schreiben und sie wieder auf den ursprünglichen Namen bewegen sich wie

tmpname=$(mktemp) 
sed -e "$lineToReplace a # $(date '+%Y-%m-%d %H:%M:%S'): replaced: $oldValue with: $newValue" -e "$lineToReplace a $newValue" -e "$lineToReplace d" "$filePath" > "$tmpname" 
sudo mv "$tmpname" "$filePath" 

oder wenn Sie die ursprünglichen Berechtigungen beibehalten möchten Sie tun können

sudo sh -c "cat '$tmpname' > '$filePath'" 
rm "$tmpname" 

oder verwenden Sie Ihre tee Ansatz wie

sudo tee "$filePath" >/dev/null <"$tmpname" 
rm "$tmpname" 
+0

Ich nehme an, dass das Schreiben der 'sed'-Ausgabe in eine Variable (anstelle einer temporären Datei) ebenfalls akzeptabel ist. – DonPromillo

+0

@DonPromillo nur so lange, wie Sie vorsichtig sind, wie Sie es zurück in die Datei bekommen, 'printf '% s'" $ contents ">" $ filePath "' sollte funktionieren, denke ich –