【问题标题】:updating a file using tee randomly fails in linux bash script在linux bash脚本中使用tee随机更新文件失败
【发布时间】:2016-08-09 13:54:47
【问题描述】:

当使用sed -e 更新配置文件的某些参数并将其通过管道传输到| tee(将更新的内容写入文件)时,这会随机中断并导致文件无效(大小为0)。

总之,这段代码用于更新参数:

# 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 次的脚本。

  • 在与 OSX 共享目录上的 Ubuntu VM (Parallels Desktop) 中,此 行为最多发生 50 次
  • 在 Ubuntu VM (Parallels Desktop) 上 Ubuntu 分区此行为最多出现 40 次
  • 在本机系统(带有 Ubuntu 的 IntelNUC)上,此行为最多发生 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

问候 唐普罗米洛

【问题讨论】:

  • 如果你要扔掉stdout,为什么还要使用tee?此外,看起来您很可能遇到了竞争条件,因为您使用tee 覆盖文件的同时使用sed 处理它。如果teesed 可以得到它之前截断文件,你将得到一个0 长度的文件。如果您的 sed 支持它,您可以让它修改文件,否则您应该将输出写入临时文件,然后将其移动到原始名称
  • 管道的两侧是异步的;你不能保证sedtee 覆盖之前完全消耗了myFile 的内容。
  • 感谢@EricRenouf 和@chepner 非常清楚地指出了这一点。我使用tee的原因基本上是对管道的好奇。

标签: linux bash ubuntu sed tee


【解决方案1】:

问题是您正在使用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"

【讨论】:

  • 我认为将sed 输出写入变量(而不是临时文件)也是可以接受的。
  • @DonPromillo 只要你小心如何将它恢复到文件中,我认为printf '%s' "$contents" &gt; "$filePath" 应该可以工作
猜你喜欢
  • 2022-07-29
  • 2023-03-07
  • 2020-05-03
  • 1970-01-01
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 1970-01-01
  • 2017-08-03
相关资源
最近更新 更多