提高删除 windows 行结尾的 Bash 循环的性能
Improve performance of Bash loop that removes windows line endings
编者注:此问题始终是关于循环性能,但原始标题导致一些回答者 - 和选民 - 相信这是关于 如何 删除 Windows 行结尾。
下面的 bash 循环只是删除了 windows 行结尾并将它们转换为 unix 并且看起来是 运行,但是它很慢。输入文件很小(4 个文件,从 167 字节到 1 kb 不等),并且都是相同的结构(名称列表),唯一不同的是长度(即一些文件有 10 个名称,其他文件有 50 个)。使用至强处理器完成此任务是否需要 15 分钟以上的时间?谢谢:)
for f in /home/cmccabe/Desktop/files/*.txt ; do
bname=`basename $f`
pref=${bname%%.txt}
sed 's/\r//' $f - $f > /home/cmccabe/Desktop/files/${pref}_unix.txt
done
输入 .txt 文件
AP3B1
BRCA2
BRIP1
CBL
CTC1
编辑
这不是重复的,因为我想问的是为什么我的 bash
循环使用 sed
删除 windows 行结尾 运行 这么慢。我并不是要暗示如何删除它们,而是在寻求可能加快循环速度的想法,我得到了很多。谢谢 :)。希望对您有所帮助。
使用实用程序 dos2unix
和 unix2dos
在 unix 和 windows 风格的行尾之间进行转换。
这对我总是有效:
perl -pe 's/\r\n/\n/' inputfile.txt > outputfile.txt
你可以像之前说的那样使用 dos2unix
或者使用这个小的 sed
:
sed 's/\r//' file
您的 'sed' 命令看起来有误。我相信尾随 $f - $f
应该只是 $f
。 运行 您编写的脚本在我的系统上挂起很长时间,但进行此更改会导致它几乎立即完成。
当然,最好的答案是使用dos2unix
,它被设计用来处理这个确切的事情:
cd /home/cmccabe/Desktop/files
for f in *.txt ; do
pref=$(basename -s '.txt' "$f")
dos2unix -q -n "$f" "${pref}_unix.txt"
done
在 Bash 中 性能 的关键是 避免一般循环,尤其是那些在每个循环中调用一个或多个外部实用程序的循环迭代.
这是一个使用 单一 GNU awk
命令的解决方案:
awk -v RS='\r\n' '
BEGINFILE { outFile=gensub("\.txt$", "_unix&", 1, FILENAME) }
{ print > outFile }
' /home/cmccabe/Desktop/files/*.txt
-v RS='\r\n'
将 CRLF 设置为输入记录分隔符,并且由于保留 ORS
,output 记录分隔符为默认值,\n
,简单地 打印 每个输入行将以 \n
. 终止
- 每次开始处理新的输入文件时都会执行
BEGINFILE
块;其中,gensub()
用于在手头输入文件的.txt
后缀前插入_unix
以形成输出文件名。
{print > outFile}
只是将 \n
终止的行打印到手边的输出文件。
注意使用multi-char。 RS
值、BEGINFILE
块和 gensub()
函数是 POSIX 标准的 GNU 扩展。
从 OP 的 sed
解决方案切换到基于 GNU awk
的解决方案是必要的,以便提供既简单又快速的单命令解决方案。
或者,这里有一个解决方案依赖于 dos2unix
来转换 Window 行尾(例如,您可以在 Debian 上安装 dos2unix
和 sudo apt-get install dos2unix
-基于系统); 除了需要 dos2unix
,它应该可以在大多数平台上运行(不需要 GNU 实用程序):
- 它只使用一个循环来构造文件名参数的数组以传递给
dos2unix
——这应该很快,因为没有调用basename
参与了; Bash-改为使用本机参数扩展。
- 然后使用单个调用
dos2unix
来处理所有个文件。
# cd to the target folder, so that the operations below do not need to handle
# path components.
cd '/home/cmccabe/Desktop/files'
# Collect all *.txt filenames in an array.
inFiles=( *.txt )
# Derive output filenames from it, using Bash parameter expansion:
# '%.txt' matches '.txt' at the end of each array element, and replaces it
# with '_unix.txt', effectively inserting '_unix' before the suffix.
outFiles=( "${inFiles[@]/%.txt/_unix.txt}" )
# Create an interleaved array of *input-output filename pairs* to be passed
# to dos2unix later.
# To inspect the resulting array, run `printf '%s\n' "${fileArgs[@]}"`
# You'll see pairs like these:
# file1.txt
# file1_unix.txt
# ...
fileArgs=(); i=0
for inFile in "${inFiles[@]}"; do
fileArgs+=( "$inFile" "${outFiles[i++]}" )
done
# Now, use a *single* invocation of dos2unix, passing all input-output
# filename pairs at once.
dos2unix -q -n "${fileArgs[@]}"
编者注:此问题始终是关于循环性能,但原始标题导致一些回答者 - 和选民 - 相信这是关于 如何 删除 Windows 行结尾。
下面的 bash 循环只是删除了 windows 行结尾并将它们转换为 unix 并且看起来是 运行,但是它很慢。输入文件很小(4 个文件,从 167 字节到 1 kb 不等),并且都是相同的结构(名称列表),唯一不同的是长度(即一些文件有 10 个名称,其他文件有 50 个)。使用至强处理器完成此任务是否需要 15 分钟以上的时间?谢谢:)
for f in /home/cmccabe/Desktop/files/*.txt ; do
bname=`basename $f`
pref=${bname%%.txt}
sed 's/\r//' $f - $f > /home/cmccabe/Desktop/files/${pref}_unix.txt
done
输入 .txt 文件
AP3B1
BRCA2
BRIP1
CBL
CTC1
编辑
这不是重复的,因为我想问的是为什么我的 bash
循环使用 sed
删除 windows 行结尾 运行 这么慢。我并不是要暗示如何删除它们,而是在寻求可能加快循环速度的想法,我得到了很多。谢谢 :)。希望对您有所帮助。
使用实用程序 dos2unix
和 unix2dos
在 unix 和 windows 风格的行尾之间进行转换。
这对我总是有效:
perl -pe 's/\r\n/\n/' inputfile.txt > outputfile.txt
你可以像之前说的那样使用 dos2unix
或者使用这个小的 sed
:
sed 's/\r//' file
您的 'sed' 命令看起来有误。我相信尾随 $f - $f
应该只是 $f
。 运行 您编写的脚本在我的系统上挂起很长时间,但进行此更改会导致它几乎立即完成。
当然,最好的答案是使用dos2unix
,它被设计用来处理这个确切的事情:
cd /home/cmccabe/Desktop/files
for f in *.txt ; do
pref=$(basename -s '.txt' "$f")
dos2unix -q -n "$f" "${pref}_unix.txt"
done
在 Bash 中 性能 的关键是 避免一般循环,尤其是那些在每个循环中调用一个或多个外部实用程序的循环迭代.
这是一个使用 单一 GNU awk
命令的解决方案:
awk -v RS='\r\n' '
BEGINFILE { outFile=gensub("\.txt$", "_unix&", 1, FILENAME) }
{ print > outFile }
' /home/cmccabe/Desktop/files/*.txt
-v RS='\r\n'
将 CRLF 设置为输入记录分隔符,并且由于保留ORS
,output 记录分隔符为默认值,\n
,简单地 打印 每个输入行将以\n
. 终止
- 每次开始处理新的输入文件时都会执行
BEGINFILE
块;其中,gensub()
用于在手头输入文件的.txt
后缀前插入_unix
以形成输出文件名。 {print > outFile}
只是将\n
终止的行打印到手边的输出文件。
注意使用multi-char。 RS
值、BEGINFILE
块和 gensub()
函数是 POSIX 标准的 GNU 扩展。
从 OP 的 sed
解决方案切换到基于 GNU awk
的解决方案是必要的,以便提供既简单又快速的单命令解决方案。
或者,这里有一个解决方案依赖于 dos2unix
来转换 Window 行尾(例如,您可以在 Debian 上安装 dos2unix
和 sudo apt-get install dos2unix
-基于系统); 除了需要 dos2unix
,它应该可以在大多数平台上运行(不需要 GNU 实用程序):
- 它只使用一个循环来构造文件名参数的数组以传递给
dos2unix
——这应该很快,因为没有调用basename
参与了; Bash-改为使用本机参数扩展。 - 然后使用单个调用
dos2unix
来处理所有个文件。
# cd to the target folder, so that the operations below do not need to handle
# path components.
cd '/home/cmccabe/Desktop/files'
# Collect all *.txt filenames in an array.
inFiles=( *.txt )
# Derive output filenames from it, using Bash parameter expansion:
# '%.txt' matches '.txt' at the end of each array element, and replaces it
# with '_unix.txt', effectively inserting '_unix' before the suffix.
outFiles=( "${inFiles[@]/%.txt/_unix.txt}" )
# Create an interleaved array of *input-output filename pairs* to be passed
# to dos2unix later.
# To inspect the resulting array, run `printf '%s\n' "${fileArgs[@]}"`
# You'll see pairs like these:
# file1.txt
# file1_unix.txt
# ...
fileArgs=(); i=0
for inFile in "${inFiles[@]}"; do
fileArgs+=( "$inFile" "${outFiles[i++]}" )
done
# Now, use a *single* invocation of dos2unix, passing all input-output
# filename pairs at once.
dos2unix -q -n "${fileArgs[@]}"