提高删除 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 行结尾 运行 这么慢。我并不是要暗示如何删除它们,而是在寻求可能加快循环速度的想法,我得到了很多。谢谢 :)。希望对您有所帮助。

使用实用程序 dos2unixunix2dos 在 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 设置为输入记录分隔符,并且由于保留 ORSoutput 记录分隔符为默认值,\n,简单地 打印 每个输入行将以 \n.
  • 终止
  • 每次开始处理新的输入文件时都会执行BEGINFILE块;其中,gensub()用于在手头输入文件的.txt后缀前插入_unix以形成输出文件名。
  • {print > outFile} 只是将 \n 终止的行打印到手边的输出文件。

注意使用multi-char。 RS 值、BEGINFILE 块和 gensub() 函数是 POSIX 标准的 GNU 扩展。
从 OP 的 sed 解决方案切换到基于 GNU awk 的解决方案是必要的,以便提供既简单又快速的单命令解决方案。


或者,这里有一个解决方案依赖于 dos2unix 来转换 Window 行尾(例如,您可以在 Debian 上安装 dos2unixsudo 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[@]}"