提高删除窗口行结尾的Bash循环的性能

时间:2015-10-07 19:06:54

标签: linux bash performance sed gawk

编者按:这个问题始终是关于循环效果 ,但是原始标题引导了一些回答者和选民 - 相信它是关于如何删除Windows行结尾。

下面的bash循环只是删除了Windows行结尾并将它们转换为unix并且似乎正在运行,但速度很慢。输入文件很小(4个文件,范围从167字节 - 1 kb),并且都是相同的结构(名称列表),唯一不同的是长度(即一些文件是10个名称,其他文件是50)。是否需要花费超过15分钟才能使用Xeon处理器完成此任务?谢谢:))

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删除Windows行结尾的sed循环运行得如此之慢。我不是故意暗示如何删除它们,是在寻求可能加速循环的想法,而且我得到了很多。谢谢 :)。我希望这会有所帮助。

5 个答案:

答案 0 :(得分:6)

使用实用程序dos2unixunix2dos在unix和windows样式行结尾之间进行转换。

答案 1 :(得分:5)

你的'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

答案 2 :(得分:4)

这对我来说总是有用的:

perl -pe 's/\r\n/\n/' inputfile.txt > outputfile.txt

答案 3 :(得分:1)

您可以按照前面的说明使用dos2unix或使用这个小sed

sed 's/\r//' file

答案 4 :(得分:1)

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输出记录分隔符的默认值为\n,只需< em>打印每个输入行将使用\n终止它。
  • 每次处理新输入文件时都会执行BEGINFILE块;在其中,gensub()用于在手头输入文件的_unix后缀之前插入.txt以形成输出文件名。
  • {print > outFile}只是将\n - 终止的行打印到手头的输出文件中。

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

或者,这是一个依赖dos2unix转换Window line-endings的解决方案(例如,您可以在基于Debian的系统上安装dos2unix sudo apt-get install dos2unix) ; 除了要求dos2unix之外,它应该适用于大多数平台(不需要 GNU 实用程序):

  • 它仅使用循环来构造文件名参数的数组以传递给dos2unix - 这应该很快,因为不涉及对basename的调用;改为使用Bash-native参数扩展。
  • 然后使用{em>单调用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[@]}"