Skip to main content
 首页 » 编程设计

python 文件操作(bash 脚本移植)

2025年05月04日10pander-it

我正在尝试重写一些我认为非常低效(更不用说不优雅)的旧 bash 脚本,并使用一些可怕的管道...也许具有真正 Python 技能的人可以给我一些指示...

该脚本使用了多个临时文件...我认为这是一种糟糕的风格并且可能可以避免...

它本质上是通过首先从顶部删除一定数量的行(丢弃标题)来操作 INPUT-FILE。
然后它拉出其中一列并:

  • 计算raws = N的个数;
  • 从这个单列文件中删除所有重复条目(我使用 sort -u -n FILE > S-FILE)。

之后,我创建了一个从 1 到 N 的连续整数索引,并使用粘贴命令将这个新索引列粘贴到原始 INPUT-FILE 中。
然后,我的 bash 脚本会为我们写入 S-FILE 的值生成百分位数排名。
我相信 Python 利用 scipy.stats,而在 bash 中我确定 S-FILE 中每个唯一条目的重复行数(dupline),然后计算 per-rank=$((100 *($counter+$dupline/2)/$length)),其中 $length= FILE 而非 S-FILE 的长度。然后我会将结果打印到一个单独的 1 列文件中(并重复相同的每个等级,因为我们有 duplines)。
然后,我会将这个带有百分位数排名的新列粘贴回 INPUT-FILE(因为我会按用于计算百分位数排名的列对 INPUT-FILE 进行排序 - 结果中的所有内容都会完美排列)。

在此之后,它进入下面的丑陋......

sort -o $INPUT-FILE $INPUT-FILE 
 
awk 'int($4)>2000' $INPUT-FILE | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 2000-$INPUT-FILE 
 
diff $INPUT-FILE 2000-$INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>1000' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 1000-$INPUT-FILE 
 
cat 2000-$INPUT-FILE 1000-$INPUT-FILE | sort > merge-$INPUT-FILE 
 
diff merge-$INPUT-FILE $INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>500' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 500-$INPUT-FILE 
 
rm merge-$INPUT-FILE 

本质上,这是执行以下操作的一种非常不优雅的 bash 方式:

  1. 从 $INPUT-FILE 中随机选择 500 行,其中第 4 列中的值大于 2000,并将其写入文件 2000-$INPUT-FILE
  2. 对于 $INPUT-FILE 中的所有剩余行,随机选择第 4 列中的值大于 1000 的 500 行并将其写入文件 1000-$INPUT-FILE
  3. 对于 $INPUT-FILE 中 1) 和 2) 之后的所有剩余行,随机选择第 4 列中的值大于 500 的 500 行并将其写入文件 500-$INPUT-FILE

再次,我希望有人能帮助我将这个丑陋的管道重新加工成 python 之美! :) 谢谢!

请您参考如下方法:

评论中的两个关键点:

(A) 文件大约有 50k 行,每行大约 100 个字符。小到足以轻松放入现代台式机/服务器/笔记本电脑系统的内存中。

(B) 作者的主要问题是关于如何跟踪已经选择的行,并且不再选择它们。

我建议三个步骤。

(1) 浏览文件,制作三个单独的列表——称它们为 u、v、w——满足每个条件的行号。这些列表可能有 500 多行,并且可能包含重复项,但我们将在步骤 (2) 中解决这些问题。

u = [] 
v = [] 
w = [] 
 
with open(filename, "r") as f: 
    for linenum, line in enumerate(f): 
        x = int(line.split()[3]) 
        if x > 2000: 
            u.append(x) 
        if x > 1000: 
            v.append(x) 
        if x > 500: 
            w.append(x) 

(2) 选择行号。您可以使用内置的 Random.sample() 从总体中选择 k 个元素的样本。我们想要删除之前选择的元素,因此要在集合中跟踪这些元素。 (“选择”集合是一个集合而不是列表,因为测试“如果 x 不在选择中”对于集合来说是 O(log(n)),但对于列表来说是 O(n)。将其更改为列表并如果您精确测量时间,您会看到速度变慢,尽管对于“仅”50k 数据点/500 个样本/3 个类别的数据集来说,这可能不是明显的延迟。)

import random 
rand = random.Random()       # change to random.Random(1234) for repeatable results 
 
chosen = set() 
s0 = rand.sample(u, 500) 
chosen.update(s0) 
s1 = rand.sample([x for x in v if x not in chosen], 500) 
chosen.update(s1) 
s2 = rand.sample([x for x in w if x not in chosen], 500) 
chosen.update(s2) 

(3) 再遍历输入文件,将编号为 s0 的行放入第一个输出文件,将编号为 s1 的行放入第二个输出文件,将编号为 s2 的行放入第三个输出文件.这在任何语言中都是微不足道的,但这里有一个使用 Python“惯用语”的实现:

linenum2sample = dict([(x, 0) for x in s0]+[(x, 1) for x in s1]+[(x, 2) for x in s2]) 
 
outfile = [open("-".join(x, filename), "w") for x in ["2000", "1000", "500"]] 
 
try: 
    with open(filename, "r") as f: 
        for linenum, line in enumerate(f): 
            s = linenum2sample.get(linenum) 
            if s is not None: 
                outfile[s].write(line) 
finally: 
    for f in outfile: 
        f.close()