2016-08-09 8 views
0

при использовании sed -e для обновления некоторых параметров конфигурационного файла и передачи его на | tee (для записи обновленного содержимого в файл) это случайное разбиение и приводит к тому, что файл недействителен (размер 0).Обновление файла с использованием tee случайным образом завершается неудачей в сценарии linux bash

В сводке, этот код используется для обновления параметров:

# 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 

Я создал скрипт, который вызывает эту команду обновления 100 раз.

  • В Ubuntu VM (Parallels Desktop) на совместно используемый каталог с OSX это происходит до 50 раз
  • в Ubuntu VM (Parallels Desktop) на Ubuntu раздела это поведение происходит до 40 раз
  • на родной системе (IntelNUC с Ubuntu), это происходит до 15 раз

Может кто-нибудь объяснить, почему это происходит?

Вот полнофункциональный сценарий, в котором вы также можете запустить эксперимент. (Все необходимые файлы создаются с помощью сценария, так что вы можете просто скопировать/вставить его в bashscriptfile и запустить его)

#!/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 

С уважением DonPromillo

+2

Почему вы используете 'tee' для этого, если вы выбрасывая' stdout'? Кроме того, похоже, что у вас есть условие гонки, скорее всего, поскольку вы используете 'tee' для перезаписи файла в то же время, что и для' sed' для его обработки. Если 'tee' обрезает файл до того, как' sed' сможет его получить, вы получите файл длины '0'. Если ваш 'sed' поддерживает его, вы можете заставить его изменить файл на месте, иначе вы должны записать вывод в файл temp, а затем переместить его на исходное имя. –

+1

. Две стороны трубы являются асинхронными; вы не можете гарантировать, что 'sed' полностью поглощает содержимое' myFile' до того, как 'tee' перезапишет его. – chepner

+0

Спасибо @EricRenouf и @chepner за то, что вы указали это довольно четко. Причина, по которой я использовал 'tee', в основном вызывает любопытство в трубопроводах. – DonPromillo

ответ

3

Проблема заключается в том, что вы используете tee перезаписать $filepath в то же время, что и sed пытается читать. Если tee обрезает его первым, то sed получает пустой файл, и вы заканчиваете файл длины 0 на другом конце.

Если у вас есть GNU sed вы можете использовать -i флаг, чтобы sed изменить файл на месте (другие версии поддерживают -i но требуют аргумента к нему). Если ваш sed не поддерживает его, вы можете иметь его записать временный файл и переместить его обратно в исходное имя, как

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" 

или если вы хотите сохранить первоначальные разрешения, которые вы могли бы сделать

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

или использовать tee подход как

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

Я предполагаю, что запись 'sed' в переменную (а не временный файл) также приемлема. – DonPromillo

+0

@DonPromillo, пока вы будете осторожны в том, как вы его возвращаете в файл, 'printf '% s'" $ contents ">" $ filePath "' должен работать, я думаю –