您好,我正在编写脚本,该脚本需要 2 个符号并用第二个符号替换第一个符号,并且该脚本也将其作为参数文件。想法是将文件中的第一个字符替换为第二个字符,这里是脚本:
char_src=$1
char_dest=$2
shift
for file
do
while read -r line
do
line=${line//$char_src/$char_dest}
echo "$line"
done < "$file"
done
问题是它打印了正确的内容,但它没有保存对文件的更改,我尝试用 done < "$file"
答案1
首先是简单的部分:从 shell 中逐行读取文件很慢,您可能想使用它tr
。man tr
详情请参阅。
其次,您需要一个临时文件。您不能同时读取和写入文件(无论如何不能从 shell 读取和写入文件)。所以你需要做这样的事情:
tr -- "$1" "$2" <"$file" >"$file".tmp
mv -f -- "$file".tmp "$file"
一个明显的问题是,如果tr
由于任何原因(比如因为$1
是空的)而失败,会发生什么。然后$file.tmp
仍然会被创建(它是在 shell 解析该行时创建的),然后$file
被它替换。
因此,一种更安全的方法如下所示:
tr -- "$1" "$2" <"$file" >"$file".tmp && \
mv -f -- "$file".tmp "$file"
现在只有成功$file
时才被替换。tr
但是如果周围有另一个文件命名怎么办$file.tmp
?好吧,它会被覆盖。这就是事情变得更加复杂的地方:技术上正确的方法是这样的:
tmp="$( mktemp -t "${0##*/}"_"$$"_.XXXXXXXX )" && \
trap 'rm -f "$tmp"' EXIT HUP INT QUIT TERM || exit 1
tr -- "$1" "$2" <"$file" >"$tmp" && \
cat -- "$tmp" >"$file" && \
rm -f -- "$tmp"
这里mktemp
创建一个临时文件;trap
确保如果脚本退出(正常或异常),该文件被清理;cat -- "$tmp" >"$file"
使用and代替来mv
确保$file
保留 的权限。如果您没有 的写入权限$file
,但临时文件最终会被删除,这仍然可能会失败。
或者,GNUsed
可以选择就地编辑文件,因此您可以执行以下操作:
sed -i "s/$1/$2/g" "$file"
然而,这并不像看起来那么简单。如果$1
或$2
对于 具有特殊含义,则上述操作将失败sed
。所以你需要先逃避它们:
in=$( printf '%s\n' "$1" | sed 's:[][\/.^$*]:\\&:g' )
out=$( printf '%s\n' "$2" | sed 's:[\/&]:\\&:g;$!s/$/\\/' )
sed -i "s/$in/$out/" "$file"
答案2
现在忽略这样一个事实:有专门为此目的而构建的工具。
char_src=$1
char_dest=$2
shift
您可能想要shift 2
在这里,一个普通的文件shift
会移动$2
到$1
并留下第一个文件名。
while read -r line
您正确使用read -r
,但请注意,使用默认值IFS
,read
将删除前导和尾随空格。
done < "$file"
<
仅重定向输入,要重定向输出,您需要> "$file2"
, 并且如许多地方所述(例如这里),重定向到同一文件只会在读取任何内容之前截断它。
关于为此制作的工具,要将单个字符更改为其他单个字符,请使用tr
。 shell 的${var//pat/repl}
构造替换了整个字符串,并且更类似于s/pat/repl/g
in sed
。