如何按没有的百分比拆分文件。行数?
Posted
技术标签:
【中文标题】如何按没有的百分比拆分文件。行数?【英文标题】:How to split file by percentage of no. of lines? 【发布时间】:2017-03-17 21:23:24 【问题描述】:如何按百分比分割文件。行数?
假设我想将文件分成 3 部分(60%/20%/20% 部分),我可以手动执行此操作,-_- :
$ wc -l brown.txt
57339 brown.txt
$ bc <<< "57339 / 10 * 6"
34398
$ bc <<< "57339 / 10 * 2"
11466
$ bc <<< "34398 + 11466"
45864
bc <<< "34398 + 11466 + 11475"
57339
$ head -n 34398 brown.txt > part1.txt
$ sed -n 34399,45864p brown.txt > part2.txt
$ sed -n 45865,57339p brown.txt > part3.txt
$ wc -l part*.txt
34398 part1.txt
11466 part2.txt
11475 part3.txt
57339 total
但我相信还有更好的方法!
【问题讨论】:
能否详细说明对可信和/或官方来源的要求?为什么您已经收到的高质量答案还不够? 赏金信息错误,应该是“希望引起注意” 百分比是否必须绝对精确,我是否正确假设您有大量行? @edmorton,你的回答没有错。这很棒,但很高兴看到不同的方法以及是否有更好的方法。 @marksetchell,它必须尽可能精确。但是,如果最后有 1-2 行由于舍入浮点数而丢失,这是可以接受的。是的,我的实际数据确实有很多,以百万计。 【参考方案1】:有一个实用程序将应该成为每个新文件的第一个行号的行号作为参数:csplit
。这是其POSIX version 的包装器:
#!/bin/bash
usage ()
printf '%s\n' "$0##*/ [-ks] [-f prefix] [-n number] file arg1..." >&2
# Collect csplit options
while getopts "ksf:n:" opt; do
case "$opt" in
k|s) args+=(-"$opt") ;; # k: no remove on error, s: silent
f|n) args+=(-"$opt" "$OPTARG") ;; # f: filename prefix, n: digits in number
*) usage; exit 1 ;;
esac
done
shift $(( OPTIND - 1 ))
fname=$1
shift
ratios=("$@")
len=$(wc -l < "$fname")
# Sum of ratios and array of cumulative ratios
for ratio in "$ratios[@]"; do
(( total += ratio ))
cumsums+=("$total")
done
# Don't need the last element
unset cumsums[-1]
# Array of numbers of first line in each split file
for sum in "$cumsums[@]"; do
linenums+=( $(( sum * len / total + 1 )) )
done
csplit "$args[@]" "$fname" "$linenums[@]"
在要分割的文件名之后,取分割文件的大小与其总和的比值,即,
percsplit brown.txt 60 20 20
percsplit brown.txt 6 2 2
percsplit brown.txt 3 1 1
都是等价的。
与问题中的情况类似的用法如下:
$ percsplit -s -f part -n 1 brown.txt 60 20 20
$ wc -l part*
34403 part0
11468 part1
11468 part2
57339 total
不过,编号从零开始,并且没有 txt
扩展名。 GNU version 支持 --suffix-format
选项,该选项将允许 .txt
扩展并且可以添加到接受的参数中,但这需要比 getopts
更精细的东西来解析它们。
此解决方案适用于非常短的文件(将两行文件分成两行),而繁重的工作由 csplit
自己完成。
【讨论】:
感谢提及csplit
和使用 getopts
(我认为,这是所有 bash 中最不受欢迎的内置函数)
@BenjaminW 感谢您的回答!不介意我给你复选标记和 EdMorton 赏金,因为他首先回答了更多的选票,但我更喜欢你的解决方案 =)【参考方案2】:
$ cat file
a
b
c
d
e
$ cat tst.awk
BEGIN
split(pcts,p)
nrs[1]
for (i=1; i in p; i++)
pct += p[i]
nrs[int(size * pct / 100) + 1]
NR in nrs close(out); out = "part" ++fileNr ".txt"
print $0 " > " out
$ awk -v size=$(wc -l < file) -v pcts="60 20 20" -f tst.awk file
a > part1.txt
b > part1.txt
c > part1.txt
d > part2.txt
e > part3.txt
将 " > "
更改为 >
以实际写入输出文件。
【讨论】:
pct
和 nrs
是什么意思?
pct
= 百分比。 nrs
= NRs = 行/记录号,输出文件号发生变化的 NR 列表。
又好又短。只是对小百分比/文件有一些问题。考虑一个有 2 行和 pct="10 90"
的文件。该脚本会将这两行写入part1.txt
。【参考方案3】:
用法
以下 bash 脚本允许您指定百分比,如
./split.sh brown.txt 60 20 20
您也可以使用占位符.
将百分比填充到 100%。
./split.sh brown.txt 60 20 .
分割后的文件被写入
part1-brown.txt
part2-brown.txt
part3-brown.txt
脚本总是生成与指定数字一样多的part
文件。
如果百分比总和为 100,cat part*
将始终生成原始文件(没有重复或缺失的行)。
Bash 脚本:split.sh
#! /bin/bash
file="$1"
fileLength=$(wc -l < "$file")
shift
part=1
percentSum=0
currentLine=1
for percent in "$@"; do
[ "$percent" == "." ] && ((percent = 100 - percentSum))
((percentSum += percent))
if ((percent < 0 || percentSum > 100)); then
echo "invalid percentage" 1>&2
exit 1
fi
((nextLine = fileLength * percentSum / 100))
if ((nextLine < currentLine)); then
printf "" # create empty file
else
sed -n "$currentLine,$nextLine"p "$file"
fi > "part$part-$file"
((currentLine = nextLine + 1))
((part++))
done
【讨论】:
【参考方案4】:BEGIN
split(w, weight)
total = 0
for (i in weight)
weight[i] += total
total = weight[i]
FNR == 1
if (NR!=1)
write_partitioned_files(weight,a)
split("",a,":") #empty a portably
name=FILENAME
a[FNR]=$0
END
write_partitioned_files(weight,a)
function write_partitioned_files(weight, a)
split("",threshold,":")
size = length(a)
for (i in weight)
threshold[length(threshold)] = int((size * weight[i] / total)+0.5)+1
l=1
part=0
for (i in threshold)
close(out)
out = name ".part" ++part
for (;l<threshold[i];l++)
print a[l] " > " out
调用为:
awk -v w="60 20 20" -f above_script.awk file_to_split1 file_to_split2 ...
将脚本中的" > "
替换为>
以实际写入分区文件。
变量w
需要空格分隔的数字。文件按该比例分区。例如"2 1 1 3"
将文件分成四个,行数比例为 2:1:1:3。任何加起来不超过 100 的数字序列都可以用作百分比。
对于大文件,数组a
可能会消耗太多内存。如果这是一个问题,这里有一个替代的awk
脚本:
BEGIN
split(w, weight)
for (i in weight)
total += weight[i]; weight[i] = total #cumulative sum
FNR == 1
#get number of lines. take care of single quotes in filename.
name = gensub("'", "'\"'\"'", "g", FILENAME)
"wc -l '" name "'" | getline size
split("", threshold, ":")
for (i in weight)
threshold[length(threshold)+1] = int((size * weight[i] / total)+0.5)+1
part=1; close(out); out = FILENAME ".part" part
if(FNR>=threshold[part])
close(out); out = FILENAME ".part" ++part
print $0 " > " out
这会通过每个文件两次。一次用于计算行数(通过wc -l
),另一次用于写入分区文件。调用和效果与第一种方法类似。
【讨论】:
【参考方案5】:我喜欢 Benjamin W. 的 csplit
解决方案,但它太长了......
#!/bin/bash
# usage ./splitpercs.sh file 60 20 20
n=`wc -l <"$1"` || exit 1
echo $* | tr ' ' '\n' | tail -n+2 | head -n`expr $# - 1` |
awk -v n=$n 'BEGINr=1 r+=n*$0/100; if(r > 1 && r < n)printf "%d\n",r' |
uniq | xargs csplit -sfpart "$1"
(if(r > 1 && r < n)
和 uniq
位用于防止创建空文件或针对小百分比、行数较少的文件或添加超过 100 的百分比的奇怪行为。)
【讨论】:
【参考方案6】:我只是按照您的指示将您手动执行的操作写入脚本。它可能不是最快的或“最好的”,但如果您了解自己现在在做什么并且可以“编写”它,那么如果您需要维护它,您可能会更好。
#!/bin/bash
# thisScript.sh yourfile.txt 20 50 10 20
YOURFILE=$1
shift
# changed to cat | wc so I dont have to remove the filename which comes from
# wc -l
LINES=$(cat $YOURFILE | wc -l )
startpct=0;
PART=1;
for pct in $@
do
# I am assuming that each parameter is on top of the last
# so 10 30 10 would become 10, 10+30 = 40, 10+30+10 = 50, ...
endpct=$( echo "$startpct + $pct" | bc)
# your math but changed parts of 100 instead of parts of 10.
# change bc <<< to echo "..." | bc
# so that one can capture the output into a bash variable.
FIRSTLINE=$( echo "$LINES * $startpct / 100 + 1" | bc )
LASTLINE=$( echo "$LINES * $endpct / 100" | bc )
# use sed every time because the special case for head
# doesn't really help performance.
sed -n $FIRSTLINE,$LASTLINEp $YOURFILE > part$PART.txt
$((PART++))
startpct=$endpct
done
# get the rest if the % dont add to 100%
if [[ $( "lastpct < 100" | bc ) -gt 0 ]] ; then
sed -n $FIRSTLINE,$LASTLINEp $YOURFILE > part$PART.txt
fi
wc -l part*.txt
【讨论】:
以上是关于如何按没有的百分比拆分文件。行数?的主要内容,如果未能解决你的问题,请参考以下文章