$ samtools view -H $BAM | grep -E "outFilterMultim...
Creado el: 21 de enero de 2026
Respondido usando GPT-5.2 Thinking por Chat01
Creado el: 21 de enero de 2026
Respondido usando GPT-5.2 Thinking por Chat01
BAM | grep -E "outFilterMultimapNmax|MultimapNmax" | head | tee HAS_NH=0
(mulocdeep_cpy37) [2024561579@c58 zs]BAM | head -n 200000 | grep -q 'NH:i:' && HAS_NH=1
tee P/qc/nh_presence.txt
if [ "BAM | head -n 200000
| awk 'BEGIN{FS="\t"}{
nh="";
for(i=12;i<=NF;i++) if(i ~ /^NH:i:/){split(i,a,":"); nh=a[3]; break}
if(nh!="" && nh!=1) bad++
}
END{
if(bad>0){print "NH_ASSERT_FAIL bad="bad; exit 1}
else{print "NH_ASSERT_PASS"}
}' | tee echo "HAS_NH=P/qc/nh_presence.txt
HAS_NH=1
(mulocdeep_cpy37) [2024561579@c58 zs] if [ "$HAS_NH" -eq 1 ]; then
samtools view BAM | head -n 200000 \ | awk 'BEGIN{FS="\t"}{ nh=""; for(i=12;i<=NF;i++) if(i ~ /^NH:i:/){split(i,a,":"); nh=a[3]; break} if(nh!="" && nh!=1) bad++ } END{ if(bad>0){print "NH_ASSERT_FAIL bad="bad; exit 1} else{print "NH_ASSERT_PASS"} }' | tee P/qc/nh_assert.txt
fi
NH_ASSERT_PASS
unique:只做验证/断言(不二次过滤)
2.1 header 里找 STAR 参数痕迹(能看到就记录)
bash
samtools view -H P/qc/star_param_hint.txt
2.2 NH 若存在:抽查是否全为 1(不是过滤)
bash
HAS_NH=0
samtools view 'NH:i:' && HAS_NH=1
echo "HAS_NH=P/qc/nh_presence.txt
if [ "BAM | head -n 200000
| awk 'BEGIN{FS="\t"}{
nh="";
for(i=12;i<=NF;i++) if(i ~ /^NH:i:/){split(i,a,":"); nh=a[3]; break}
if(nh!="" && nh!=1) bad++
}
END{
if(bad>0){print "NH_ASSERT_FAIL bad="bad; exit 1}
else{print "NH_ASSERT_PASS"}
}' | tee $P/qc/nh_assert.txt
fi这个结果能验证我要验证的东西吗?
整体上能验证你要验证的点(“这份 BAM 里应该只剩 unique mapping,不做二次过滤,只做断言/抽查”),而且你这次的输出 NH_ASSERT_PASS 与 header 里的 --outFilterMultimapNmax 1 是一致的。
不过也有几个边界/漏洞要知道(不影响你目前的“抽查式验证”的结论,但影响“100% 全量断言”的强度):
你抓到了 STAR 的命令行里有:
--outFilterMultimapNmax 1这说明当时 STAR 的配置意图是:只保留最多 1 个定位(即 unique)能通过过滤的 reads。
这属于“参数痕迹/证据”,但它本质上是记录,不是对 BAM 内容的强约束(理论上 header 可被改写,虽然实际不常见)。
你做的是:
NH:i:,则断言所有 NH==1结果 NH_ASSERT_PASS 表示:
在抽查范围内,所有带 NH 标签的记录都满足 NH=1(没有发现 NH≠1 的多定位残留)。
这对“STAR unique 输出是否如预期”是很直接的佐证。
如果 BAM 很大,前 200k 没问题不代表后面一定没问题。
✅ 更强但仍然高效的做法:全量扫描但遇到第一个异常就立刻退出(不会真的扫完整个文件,除非确实都 OK)。
你现在是:
nh="" ⇒ 不计入 bad如果你想验证“所有主比对记录都必须有 NH 且等于 1”,那应该把 nh=="" 也判 fail(至少对 mapped primary)。
一般来说,多重比对会带来 secondary(0x100)或 supplementary(0x800),而这些有时也值得做个计数断言。
不过在 STAR 且 outFilterMultimapNmax 1 的情况下,通常不会有多重比对残留,但做个计数更踏实。
bashsamtools view -@ 8 -F 2308 "$BAM" \ | awk 'BEGIN{FS="\t"} { nh=""; for(i=12;i<=NF;i++) if($i ~ /^NH:i:/){split($i,a,":"); nh=a[3]; break} if(nh=="" || nh!=1){ print "NH_ASSERT_FAIL qname="$1" nh="nh > "/dev/stderr"; exit 1 } } END{print "NH_ASSERT_PASS_ALL_PRIMARY_MAPPED"}'
-F 2308 = 排除:unmapped(4) + secondary(256) + supplementary(2048)如果你不想强制 “NH 必须存在”,只想断言 “若存在 NH,则必须为 1”,把
nh==""那个条件去掉即可。
bashsamtools view -c -f 256 "$BAM" | awk '{print "SECONDARY_COUNT="$1}' samtools view -c -f 2048 "$BAM" | awk '{print "SUPPLEMENTARY_COUNT="$1}'
NH_ASSERT_PASS + header 显示 outFilterMultimapNmax 1,可以验证“抽查范围内确实是 unique 输出,没有发现 NH≠1”。如果你告诉我:你对 supplementary(嵌合/剪接导致的分裂比对)是“允许/不允许”,我可以把 -F 的策略和断言口径帮你定成最贴合你项目定义的“unique”。
这是卡住了吗
pythonfrom PIL import Image, ImageEnhance img=Image.open('/mnt/data/image.png') img.size (1629, 111)
pythonimport matplotlib.pyplot as plt, numpy as np arr=np.array(img) arr.mean(), arr.min(), arr.max() (229.16327192754449, 51, 255)
python# zoom in crop=img.crop((0,0,1629,111)) # enlarge crop2=crop.resize((1629*2,111*2)) crop2.size (3258, 222)
pythonimport matplotlib.pyplot as plt plt.figure(figsize=(16,3)) plt.imshow(crop2) plt.axis('off') plt.show()
看截图不像“卡住在输入”,而是命令正在跑:你已经把最后一行敲完了(没有再出现 > 续行提示),现在光标停在下一行但还没回到 shell 提示符,通常就是管道在执行中。
这条会不会“看起来像卡住”很正常,因为:
samtools view ... "$BAM_TEST" 会顺序扫完整个 BAM(即使你 -f 64 只要 read1,也得把全文件过一遍)bedtools bamtobed 单线程,往往是瓶颈,samtools 会被管道反压住,看起来“没动静”你可以用下面几招确认到底是在跑还是死了(不破坏进程):
开另一个终端(推荐):
bashls -lh "$W/reads.blocks.r1.bed" wc -l "$W/reads.blocks.r1.bed"
隔几十秒再跑一次:如果 size / 行数在涨,就在正常跑。
bashps -u "$USER" -o pid,etime,pcpu,pmem,cmd | egrep 'samtools view|bedtools bamtobed|awk' | grep -v egrep
pcpu 有值、etime 在走:正常pcpu 接近 0 但文件在涨:多半是 I/O 慢(NFS/磁盘)也正常pcpu 0 且文件长时间不涨:才更像“卡住/堵住”bashsamtools view -b -F "$EXCL" -f 64 "$BAM_TEST" \ | head -c 1000000 > /dev/null && echo OK
如果这都很快 OK,说明 samtools 输出没问题;慢的话就是 BAM/IO 本身慢。
你现在 -b 会把输出重新压缩成 BAM 再喂给 bedtools;管道里更快的写法一般是用 uncompressed BAM:
bashsamtools view -u -F "$EXCL" -f 64 "$BAM_TEST" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed"
如果你愿意,把下面两条的输出贴一下我就能更确定是“正常慢”还是“真堵住”:
ls -lh "$W/reads.blocks.r1.bed"ps -u "$USER" -o pid,etime,pcpu,cmd | egrep 'samtools view|bedtools bamtobed' | grep -v egrep这个报错啥意思
这不是报错,是 conda 的提示/警告(WARNING):告诉你 conda 有更新版本。
25.7.025.11.1bashconda update -n base -c conda-forge conda
bashconda config --set notify_outdated_conda false
如果你是因为“卡住/不动了”才注意到这段输出:通常真正卡住的是后面还在下载/安装/解依赖;你可以把它后续有没有 Proceed ([y]/n)? 或下载进度那段也截一下,我再帮你判断是否真卡。
他这个-f 64 啥意思
samtools view 里的 -f 64 是 按 FLAG 过滤,意思是:
✅ 只保留 FLAG 中包含 64 这个位的记录
也就是:只输出 paired-end 里的 read1(第一端 / R1 / mate1)
在 SAM/BAM 里:
64 = 0x40 = this is the first read in the pair128 = 0x80 = this is the second read in the pair(read2 / R2)所以你这条:
bashsamtools view -b -F $EXCL -f 64 "$BAM_TEST"
等价于:
👉 “只取 read1 的比对记录(并且还会额外排除掉 $EXCL 指定的那些FLAG)”
bashsamtools view -f 64
bashsamtools view -f 128
-f 64 / -f 128如果你愿意把 $EXCL 的值贴出来,我也可以帮你解释 -F $EXCL 具体排除了哪些 reads(通常是 unmapped/secondary/supplementary 等)。
能不能改个参数,多加点核心数,让他跑的快哦一点,现在太慢了
能不能改个参数,多加点核心数,让他跑的快哦一点,现在太慢了
可以的,而且确实有用 👍
你这条命令现在慢,主要瓶颈在 samtools 和 I/O,可以通过 加线程 明显改善一部分。
👉 给 samtools view 加 -@ 线程参数
👉 同时把 -b 改成 -u(不压缩)
这是你现在这条管道里性价比最高的加速方式
bashsamtools view -b -F $EXCL -f 64 "$BAM_TEST" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
samtools view -b
bedtools bamtobed
bashsamtools view -@ 8 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
-@ 8-u📌 通常能比原来 快 1.5–3 倍(取决于磁盘)
bedtools bamtobed 不能多线程awk 也基本是单线程所以:
再怎么加
-@,超过 8–16 核收益就很小了
-@ 8 或 -@ 12-@ 4~8 就够了(再大是浪费)你可以跑这个看看 CPU 是否在用:
bashtop -u $USER
✔ 能加速
✔ 只需要改 samtools 一个参数就行
✔ 最推荐组合:samtools view -@ N -u
如果你愿意,我也可以:
你现在用的机器大概有多少核?(nproc 一下就行)
16个核心吧
那就直接用 8 或 12 线程比较合适(16 也能用,但收益通常不再线性,反而容易把 I/O 或 bedtools 卡住)。
把你的命令改成这样就行(推荐):
bashsamtools view -@ 12 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
要点:
-@ 12:samtools 解压/读取用 12 线程(16 核机器很常用的取值)-u:不压缩输出(走管道最快),比你原来的 -b 快不少bedtools bamtobed 还是单线程,所以别指望把 -@ 拉到 32 还能快很多如果你发现 12 线程 CPU 很满但速度没提升,说明瓶颈在磁盘或 bedtools;这时可以试试 -@ 8(有时反而更稳更快):
bashsamtools view -@ 8 -u -F "$EXCL" -f 64 "$BAM_TEST" | ...
想快速判断是不是 I/O 卡住:跑的时候看一下输出文件是否在涨(涨就没卡住):
bashls -lh "$W/reads.blocks.r1.bed" wc -l "$W/reads.blocks.r1.bed"
你现在用的机器大概有多少核?(nproc 一下就行),具体命令,我本来的意思让你弄16个核心,这样就先看看吧
明白 👍 那就直接按 16 个核心来跑,不纠结了。
bashnproc
如果输出是 16(或 ≥16),那下面的用法就是合理的。
这是在你原命令基础上只做加速、不改语义的版本:
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
-@ 16-u-b 快很多)-f 64:只取 read1-F $EXCL:排除你定义的那些 FLAGbedtools bamtobed -split:拆 exon/blockawk:给 read 名加 /1bedtools bamtobed 限制(它是单线程)跑的时候开个窗口:
bashtop -u $USER
看到:
samtools view 进程占好几个核心(>100% CPU) → 正常bedtools bamtobed 基本 100% 左右 → 正常瓶颈如果你后面还觉得慢,下一步可以考虑的是:
你现在这个步骤是在整个流程里最慢的一步之一,慢不是你操作问题,这点你可以放心。
还要加进度条啊
好,这个可以加,而且很实用 😄
在你这个 samtools → bedtools → awk 的长管道里,最靠谱的进度条方案就是 pv。
下面我直接给你可直接用的版本。
pv(按 BAM 总 reads 数显示进度)bashTOTAL=$(samtools idxstats "$BAM_TEST" | awk '{s+=$3+$4} END{print s}') echo "TOTAL_READS=$TOTAL"
说明:
$3= mapped$4= unmapped- 两个加起来 = BAM 里记录总数
- 这是 O(索引) 的操作,很快
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
pv 会显示:
类似这样:
3.2M 18% [=====> ] 120k/s ETA 0:21:34
你现在有 -f 64(只取 read1),但:
pv -l -s "$TOTAL" 用的是 整个 BAM 的总行数👉 实际经验:完全够用,比“黑屏等”强太多
如果你非要“read1 精确进度”,必须先数一遍 read1:
bashR1_TOTAL=$(samtools view -c -f 64 "$BAM_TEST")
然后:
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$R1_TOTAL" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
⚠️ 缺点:
samtools view -c -f 64 本身要 扫一遍 BAM👉 除非你对 ETA 特别苛刻,否则不推荐
以后你可以直接复用这套:
bashTOTAL=$(samtools idxstats "$BAM" | awk '{s+=$3+$4} END{print s}') samtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM" \ | pv -l -s "$TOTAL" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{print}' > out.bed
如果你愿意,我也可以帮你:
你现在这个需求提得非常对 👍,长管道没进度条真的很反人类。
TOTAL=BAM_TEST" | awk '{s+=4} END{print s}')
(mulocdeep_cpy37) [2024561579@c54 zs]TOTAL"
TOTAL_READS=265021990
ps -u TOTAL_R1=EXCL -f 64 "$BAM_TEST")
(mulocdeep_cpy37) [2024561579@c58 zs]TOTAL_R1"
R1 reads = 132510995
这怎么跟刚才不一样呢
这俩其实是一致的,不是哪里算错了。
你这次得到的数:
TOTAL_READS = 265,021,990R1 reads = 132,510,995注意:
132,510,995 × 2 = 265,021,990(刚好一模一样)
所以发生的事情是:
samtools idxstats 你算到的是 整个 BAM 的记录总数(R1+R2)你用的是:
bashsamtools idxstats "$BAM_TEST" | awk '{s+=$3+$4} END{print s}'
这相当于把 BAM 里(通过索引)统计到的所有 read 记录都加起来了——对于 paired-end 来说通常就是 R1 + R2 两端的总和。
samtools view -c ... -f 64 你算的是 只取 read1(R1),而且还应用了 -F $EXCL你用的是:
bashsamtools view -c -F $EXCL -f 64 "$BAM_TEST"
-f 64:只数 pair 里的第一端(R1)-F $EXCL:再把你排除的那些 FLAG 去掉所以它天然应该接近 TOTAL 的一半(paired-end 的 R1、R2 数量通常相等),你这里刚好完美对半,说明 BAM 很“规整”。
如果你这条管道确实是 -f 64 只跑 R1:
✅ 用 TOTAL_R1=132510995 最准确
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL_R1" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
如果你不想为了算 TOTAL_R1 再扫一遍 BAM(很慢):
✅ 也可以用 TOTAL_READS,只是进度会“慢一倍”(因为总量按 R1+R2 算的)
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL" ...
你贴的日志里,一个是在 c54,一个在 c58。如果这是集群不同节点,只要 $BAM_TEST 指向同一个共享路径文件,结果就应该一致;你这里数值能严格对半,也侧面说明文件是一致的。
如果你把 $EXCL 的具体值贴一下,我还能帮你确认它到底排除了哪些(比如是否排除 secondary/supp/unmapped),以及会不会影响计数口径。
我本身要执行这个命令
行,那就把你要跑的命令直接整理成**“16 核 + 进度条 + R1 精确总量”**的一条可执行版本(你前面已经算出 TOTAL_R1=132510995 了)。
bashsamtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL_R1" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
注意:这会先
samtools view -c ...扫一遍 BAM 统计 R1,所以总共会读 BAM 两次(但进度更准)。
bashTOTAL_R1=$(samtools view -c -F "$EXCL" -f 64 "$BAM_TEST") echo "R1 reads = $TOTAL_R1" samtools view -@ 16 -u -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL_R1" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
会显示已处理行数/百分比/速度/ETA。
如果你跑起来发现进度条不动,90% 是输出文件没增长或管道堵住;你可以随手看一眼:
bashls -lh "$W/reads.blocks.r1.bed"
我本身要执行这个命令
samtools view -b -F BAM_TEST"
| bedtools bamtobed -split -i -
| awk 'BEGIN{FS=OFS="\t"}{4"/1";print}' > "$W/reads.blocks.r1.bed"
samtools view -b -F BAM_TEST"
| bedtools bamtobed -split -i -
| awk 'BEGIN{FS=OFS="\t"}{4"/2";print}' > "$W/reads.blocks.r2.bed"
cat "W/reads.blocks.r2.bed" > "W/reads.blocks.bed"
但是我一时为了加速申请了32个核心,你可以用30个核心来跑,可能你会说超过16个对提速帮助不大,但是我有没有别的用处所以用30个就行,而是加一个进度条pv 你也说了是13开头那个数字是正确的 然后三个命令我们先写第一个
可以,先把**第一个(R1)**写成「总共用 30 核 + pv 进度条(用你那个 132510995)」的版本。
⚠️ 关键点:你原来 samtools view -b 输出的是 BAM(二进制),pv -l 没法按“行/reads”计数;所以要想用 pv -l -s 132510995 这种“按 reads 数显示进度”,需要先把 BAM 转成 SAM(文本、每条一行)给 pv 数行,再转回 BAM 给 bedtools 吃。语义不变,只是多了一次格式转换来换进度条。
bashTOTAL_R1=132510995 # 你刚才算出来那个“13 开头”的数 samtools view -@ 28 -h -F "$EXCL" -f 64 "$BAM_TEST" \ | pv -l -s "$TOTAL_R1" \ | samtools view -@ 2 -u - \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' \ > "$W/reads.blocks.r1.bed"
-@ 28 + -@ 2:两段 samtools 加起来用 30 个线程(更贴合你“申请了 32 核、先用 30 核”的想法)-h:把 header 一起输出,保证后面的 samtools view -u - 能正确生成 BAM-u:输出未压缩 BAM,给管道/bedtools 更快想确认 pv 在不在:
bashcommand -v pv || echo "pv not found"
你跑完这个 R1 没问题的话,我再按同样方式把第二个(R2)和后面的 cat 一起给你串起来(也可以给总进度)。
第二个是不是也得 先算下一共有多少 ,然后再加进度条算blocks
对,第二个(-f 128)如果你也想要“百分比/ETA 精确”,最好也有一个 TOTAL_R2。但你这个 BAM 很规整的话,通常 TOTAL_R2 == TOTAL_R1(你现在就是严格 2 倍关系),所以也可以直接复用 TOTAL_R1。
另外你提到“算 blocks 的进度”:这个要注意——
pv -l 能精确数“输入的 reads/alignments”(SAM 一行一个比对记录)bedtools bamtobed -split 输出的 blocks 行数是不固定的(一个 read 可能拆成 1 行、2 行、很多行,取决于 CIGAR/剪接),所以没法事先给出一个准确的 blocks 总数来做百分比bashTOTAL_R2=$(samtools view -c -F "$EXCL" -f 128 "$BAM_TEST") echo "R2 reads = $TOTAL_R2" samtools view -@ 28 -h -F "$EXCL" -f 128 "$BAM_TEST" \ | pv -l -s "$TOTAL_R2" \ | samtools view -@ 2 -u - \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' \ > "$W/reads.blocks.r2.bed"
这里我还是按你说的“用 30 个核”分配:
28 + 2 = 30。
如果你确认 R1/R2 数完全一样(你现在就是),那也可以直接:
bashTOTAL_R2=$TOTAL_R1
在 bedtools 后面再插一个 pv -l(不带 -s)就行:
bashsamtools view -@ 28 -h -F "$EXCL" -f 128 "$BAM_TEST" \ | pv -l -s "$TOTAL_R2" \ | samtools view -@ 2 -u - \ | bedtools bamtobed -split -i - \ | pv -l \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' \ > "$W/reads.blocks.r2.bed"
pv:reads 进度(有百分比/ETA)pv:blocks 行输出速度(没有百分比,但能看到 lines/s 在跑)你现在这套“按 reads 的 pv 百分比”的做法之所以要 -h 输出 SAM,是为了让 pv -l 真的能“按行数 reads”来计数;这会比纯 BAM 流多一点开销。
如果你后面觉得“加了 pv 反而更慢”,我可以给你换成 按 BAM 字节数的 pv(几乎不减速,也有百分比/ETA)——但那个进度是按文件读了多少字节,不是按 reads 数。
你先把 R2 这条跑通的话,把你想要的“最终三条命令整合版(R1+R2+cat)”我也可以按同样口径一次性写给你。
这是卡住了吗
大概率没卡住,这是正常现象。
你这句:
bashTOTAL_R2=$(samtools view -c -F "$EXCL" -f 128 "$BAM_TEST")
samtools view -c 为了得到“精确计数”,需要把整个 BAM 扫一遍(你这个量级 2.65e8 条记录),而且因为你用的是 命令替换 $(),在它跑完之前终端不会输出任何东西、也不会回到提示符,所以看起来就像“挂住了”。
开另一个终端/窗口执行:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'samtools view -c' | grep -v grep
如果能看到进程、etime 在增长、pcpu 有数值(哪怕不高),就是在跑。
给 samtools view 加 -@:
bashTOTAL_R2=$(samtools view -@ 30 -c -F "$EXCL" -f 128 "$BAM_TEST") echo "R2 reads = $TOTAL_R2"
这会让解压/读取部分多线程,通常会快一些(最终仍受磁盘 I/O 影响)。
你前面已经算出:
TOTAL_READS = 265021990TOTAL_R1 = 132510995
而且 TOTAL_READS = 2 × TOTAL_R1,说明这份数据非常规整(R1/R2 对称)。所以第二个可以直接:
bashTOTAL_R2=$TOTAL_R1 echo "R2 reads = $TOTAL_R2"
(如果你特别担心 -F "$EXCL" 会让 R1/R2 不完全相等,那就用上面的 -@ 30 精确数一次。)
如果你把 echo "$EXCL" 的值贴一下,我还能顺便判断:它是否可能导致 R1/R2 不对称(比如只排除了某些只在一端出现的标记)。
bedtools intersect -a "INTRON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' \
| sort -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort -k1,1 -k2,2 > "W/read_gene.intronContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' \ | sort -k1,1 -k2,2 > "W/intron.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/intron.internal.cand.read_gene.tsv"
| sort -k2,2 -k1,1 > "$W/intron.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' "W/intron.internal.unique.read_gene.tsv"
| sort -k1,1 > "$W/intron_internal_by_gene.tsv"
head -n 5 "$W/intron_internal_by_gene.tsv"
这段代码能加速吗 改成30核心吗
可以加速,但要先讲清楚:这段里 bedtools intersect / join / awk 本身都不支持“加线程参数”(它们基本单线程)。想用到你申请的 30 核,主要靠两招:
reads.blocks.bed 分成 30 份并行跑 30 个 bedtools intersect(这通常是最慢的一步,收益最大)sort 改成 GNU sort 的并行模式:sort --parallel=30(sort 在大文件上也很吃 CPU)下面给你一个“可直接替换”的版本:先只改你第一段(生成 read_gene.intronContainedBlockN.tsv),其它段我也顺手给出怎么改线程。
bashT=30 export LC_ALL=C TMPDIR="${W}/tmp.parallel.$$" mkdir -p "$TMPDIR" # 1) 把 A 文件按行均分成 30 份(连续切分,后续即使需要也容易保持局部有序) split -n l/$T --additional-suffix=.bed "$W/reads.blocks.bed" "$TMPDIR/A." # 2) 并行跑 bedtools intersect(30 个任务),先输出到一个中间文件 # 注意:这里的 awk 仍然做你原来的字段抽取:$4,$10,$1,$2,$3 # --bar 是进度条(GNU parallel 自带) parallel -j "$T" --bar \ "bedtools intersect -a {} -b '$INTRON' -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS=\"\t\"}{print \$4,\$10,\$1,\$2,\$3}'" \ ::: "$TMPDIR"/A.*.bed \ > "$TMPDIR/intersect.tsv" # 3) 后面保持你原逻辑,但把 sort 开并行、加速 sort --parallel="$T" -T "$TMPDIR" -u "$TMPDIR/intersect.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.intronContainedBlockN.tsv" rm -rf "$TMPDIR"
parallel -j 30 会启动 30 个 bedtools intersect(每个 1 核),总共吃满你的核数sort --parallel=30 会让 sort 自己开线程parallel --bar 已经有一个“整体进度条”(30 个分片完成多少)pv 那种 ETA:可以把 parallel --bar 当作进度条用,通常够用了pv 也能加,但在并行场景下意义没那么直观)你原来的后续基本是 join/sort/awk,其中只有 sort 能并行。所以建议把所有 sort 都换成:
sort --parallel=30 -T "$TMPDIR"(尽量用本地盘当临时目录)例如你这些地方:
bashjoin -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort -k1,1 -k2,2 > "$W/intron.internal.cand.read_gene.tsv"
改成(同样加速 sort):
bashexport LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/intron.internal.cand.read_gene.tsv"
其它 sort 同理。
-j 30 先降到 -j 16~24 更稳。但你既然申请了 32 核,先按 30 试完全 OK。如果你愿意把:
wc -l "$W/reads.blocks.bed"wc -l "$INTRON"-j 和 sort --buffer-size(有时能再快一截)。这确实卡住了吧
从你截图看,不是“卡住”,而是你现在还在 shell 的续行输入模式里:提示符是 >,说明上一行还没结束(通常是引号/反斜杠没闭合,或你手动加了行尾 \ 让 shell 继续等下一行)。
你这条里最可疑的是最后的 \:
bashawk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort -k1,1 > "$W/read.blockN.tsv"
如果你只想两行管道,不需要在第一行末尾写 \(| 本身就可以换行继续)。你写了 \ 的话,shell 会等你继续补完同一条命令;如果你又不小心让引号不配对,就会一直出现 >。
>?bashexport LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
说明:这一步里 awk 还是单线程,真正能吃多核的是
sort --parallel=30。LC_ALL=C也能让 sort 更快。
>:一定是在等你输入(不是在跑)。>,而是没有提示符、或者进程在后台;你可以用:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep
如果你把你实际敲进去的那一行(从 awk 到末尾)原样贴出来,我也能帮你定位到底是哪一个引号/反斜杠导致 > 的。
这样的
你这个截图里还是没有“跑起来”,原因很明确:你又进入了 shell 的续行模式(左边有 >)。也就是说 bash 还在等你把这一条命令补完,所以看起来像卡住。
罪魁祸首是你在这一行末尾加了反斜杠 \:
bashawk '...' "$W/reads.blocks.bed" \
在管道里换行 不需要 这个 \(| 本身就表示还没结束)。你加了 \,bash 就会继续等“同一条命令”的后续输入,于是出现 >。
>(否则你怎么敲都可能黏在同一条里)按一次:
回到正常提示符(不是 >)再执行下面的正确版本。
"$W/reads.blocks.bed" 后面写 \bashexport LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
你也可以把它写成一行,最不容易出
>:
bashexport LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" | sort --parallel="$T" -T "$TMPDIR" -k1,1 > "$W/read.blockN.tsv"
跑起来后不会有 >,你可以另开窗口看:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep
或者看输出文件是否在增长:
bashls -lh "$W/read.blockN.tsv"
这一段里:
awk 是单线程(不能用到 30 核)sort --parallel=30 能吃多核,但是否明显加速取决于输出行数和磁盘临时空间你先按上面修正把命令跑起来;如果你愿意,我也可以给你把这一步加一个“进度条/速度显示”(对 awk 这类没有天然进度的,需要换一种写法)。
111
你这次这条**已经是“一行命令”**了,所以不会再出现 > 续行卡住的问题 ✅
现在屏幕上啥也不输出、光标不回到提示符,大概率是在正常跑(这条会读完整个 reads.blocks.bed,awk 还要在内存里攒一个哈希表,文件大时会很久)。
不过我看到你这一行里有个致命小问题:你把 awk 的程序用双引号包住了:
bashawk "BEGIN{FS=OFS="\t"}{c[$4]++} END{...}"
这会让 shell 先把里面的 "\t" 当成引号结束/开始,语法其实是不对的(有时会直接报错,有时表现很怪)。
bashexport LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; \ awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
另开一个窗口(或者同窗口后台)看进程:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep
再看输出文件有没有生成/变大:
bashls -lh "$W/read.blockN.tsv"
因为 awk 是在“计数聚合”,没法事先知道会输出多少 key,所以百分比 ETA 不好做。但你可以至少加个“读入速度条”(按输入行数/字节):
bashexport LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; \ pv "$W/reads.blocks.bed" \ | awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
这会显示正在读 reads.blocks.bed 的速度/已读字节/ETA(按文件大小)。
如果你把 ls -lh "$W/reads.blocks.bed" 的大小贴一下,我可以更准确告诉你这一步大概是 I/O 慢还是 awk 内存聚合慢,然后给你进一步的“真加速版”(比如改成排序后分组计数,避免 awk 哈希爆内存)。
后台看不到
你这张图说明两件事:
awk/sort 在跑(所以 ps ... | grep -E 'awk|sort' 什么都抓不到)ls -lh "$W/read.blockN.tsv" 变成了去找 /read.blockN.tsv,这意味着 $W 变量在这个终端里是空的/没设置(所以路径被展开成 /read.blockN.tsv)。所以不是“后台看不到”,而是:命令没跑起来 + 变量没了。
在你现在这个 shell 里跑:
bashecho "W=$W" echo "BAM_TEST=$BAM_TEST" echo "EXCL=$EXCL"
如果 W= 是空的,就解释了为什么会去访问 /read.blockN.tsv。
你截图里提示符从
(mulocdeep_cpy37)变成了(base),很像是你换了环境/换了会话/换了节点,导致变量没继承。
假设你实际工作目录是当前目录下的某个 W(你自己知道 W 应该指向哪),比如:
bashW=/path/to/your/workdir # <-- 改成你真实的 W export W
然后执行这条 正确的(单行可粘贴):
bashexport LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; \ awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
立刻开看:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep ls -lh "$W/read.blockN.tsv"
强烈建议你以后都这么写(避免 silent bug):
bash: "${W:?W is empty. set W first}" : "${T:=30}"
放在脚本开头,W 为空会直接报错退出,而不是悄悄写到根目录。
如果你把 echo "W=$W" 的输出贴一下(以及你希望 W 指向哪里),我可以直接把三段命令都按你现在这个会话的路径一次性写成可跑版本。
对上了:你现在这个终端里 W / BAM_TEST / EXCL 全是空的,所以:
"$W/read.blockN.tsv" 被展开成 /read.blockN.tsv/reads.blocks.bed 之类)这通常是因为你换了会话/节点/环境(你提示符也从 (mulocdeep_cpy37) 变成 (base) 了),变量没继承过来。
你需要在当前这个终端重新 export 这三个变量(值用你自己真实的路径/数值):
bashexport W=/你的工作目录路径 export BAM_TEST=/你的bam路径/xxx.bam export EXCL=2308 # 例子!用你原来那个EXCL值
不知道它们原来是什么的话,你可以用下面方法找回:
bashpwd ls
看看有没有 reads.blocks.bed、star/、qc/ 这些你前面提到的目录/文件。
reads.blocks.bed 在哪(从你当前目录往下找)bashfind . -maxdepth 4 -name "reads.blocks.bed" -o -name "reads.blocks.r1.bed" -o -name "reads.blocks.r2.bed" 2>/dev/null | head
找到后,把 W 设成那个文件所在目录。
bashecho "W=$W" echo "BAM_TEST=$BAM_TEST" echo "EXCL=$EXCL" ls -lh "$W/reads.blocks.bed"
(这是你刚才要做的 read.blockN.tsv 生成)
bash: "${W:?W is empty}" export LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
你现在不是“后台看不到”,而是 变量全丢了;先把 W/BAM_TEST/EXCL 在这个终端重新设回来,一切就正常了。
你把 find 找到的 reads.blocks.bed 路径贴出来,我可以直接帮你把 export W=... 写成正确那条(不用你猜)。
我那个是第二个窗口
啊那就完全解释通了:第二个窗口是新的 shell,会话里当然没有你第一个窗口里 export 的那些变量(默认不会自动继承),所以 W/BAM_TEST/EXCL 都是空的,这就是你看到 /read.blockN.tsv 的原因。
你现在有两条路,选一个最顺手的:
在第一个窗口把这三行输出出来(或你自己知道值也行):
bashecho "export W=$W" echo "export BAM_TEST=$BAM_TEST" echo "export EXCL=$EXCL"
把输出复制到第二个窗口执行一下,就同步好了。
然后再跑你要的命令就不会变成 /read.blockN.tsv 了。
如果你不想每次复制变量,可以在第一个窗口生成一个 env 文件:
bashcat > ~/myvars.sh <<'EOF' export W='"$W"' export BAM_TEST='"$BAM_TEST"' export EXCL='"$EXCL"' EOF
然后在第二个窗口:
bashsource ~/myvars.sh
之后两个窗口都能用同一套变量(除非你再改)。
你在第二个窗口看到的不是卡住,是变量空导致根本没跑对路径。
等你把变量导入后,这条才会真的开始跑:
bashexport LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
跑起来后再用第二个窗口看进程才有意义:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep
要不你直接把第一个窗口里这三行的输出贴一下(echo "export W=$W" 那种),我就能把你第二个窗口该执行的完整 set+run 版本一次性给你。
echo "export W= echo "export BAM_TEST= echo "export EXCL=$EXCL"
export EXCL=2308
好,第二个窗口直接把这三行 copy 过去执行,变量就有了:
bashexport W=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474 export BAM_TEST=/datapool/home/2024561579/zs/star/SRR31004474/SRR31004474_Aligned.sortedByCoord.out.bam export EXCL=2308
然后你在第二个窗口再跑你要的那一步(生成 read.blockN.tsv,sort 用 30 核):
bashexport LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
跑之前可以顺手确认文件在不在:
bashls -lh "$W/reads.blocks.bed"
这样就不会再出现去找 /read.blockN.tsv 这种情况了。
export LC_ALL=C
T=30
TMPDIR="{W}/tmp.sort.$$"; mkdir -p "TMPDIR"
awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}' "W/reads.blocks.bed"
| sort --parallel="TMPDIR" -k1,1 \
"$W/read.blockN.tsv"
在第二个窗口看一眼呗,他在不在跑
我这边没法直接看到你机器上的进程,但你现在这个状态,我可以准确告诉你该怎么看、以及看到什么才算“在跑”。你照着下面在第二个窗口敲就行。
在第二个窗口执行:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort' | grep -v grep
awk 进程sort --parallel=30 进程etime 在增长pcpu > 0(sort 可能很高,awk 一般 100% 左右)例如(示意):
text12345 R 00:03:21 98.5 awk BEGIN{FS=OFS=... 12346 R 00:03:21 750.2 sort --parallel=30 ...
👉 这就说明:确实在跑,而且 30 核的 sort 吃上了
那说明:
awk 因为输入文件问题直接退出了这时继续第 2 步确认。
bashls -lh "$W/read.blockN.tsv"
$W/reads.blocks.bed 不存在)你也可以隔 10 秒敲一次看看 size 有没有变化。
bashls -lh "$W/reads.blocks.bed" wc -l "$W/reads.blocks.bed"
如果是 几千万 / 上亿行:
$4 全部攒在内存里,本来就不快> 提示符 → 不是续行卡住这一步 awk 是绝对瓶颈,单线程,无法再快。
30 核只有 sort 能用上,awk 用不上,这是工具本身限制,不是你操作问题。
如果你之后觉得这一步还是太慢,我可以给你一个:
你先按上面 1️⃣ / 2️⃣ 看一眼,把看到的 ps 或 ls -lh 输出贴一行回来,我可以直接帮你判断:
👉 是在正常跑 / 已跑完 / 需要换实现
对,你这个截图基本可以确定:那条管道没在持续跑,而是很快就结束了,并且因为你用了 > "$W/read.blockN.tsv",所以哪怕上游报错/没输出,也会先把输出文件创建出来(于是你看到一个 0 字节的 read.blockN.tsv)。
最常见原因有 3 个(按概率从高到低):
你按下面这套检查,基本 30 秒就能定位。
在第二个窗口跑:
bashls -lh "$W/reads.blocks.bed" head -n 2 "$W/reads.blocks.bed" wc -l "$W/reads.blocks.bed"
ls 直接报 No such file → 就是原因 #1wc -l 是 0 → 输入本来就是空的head 能看到内容但输出仍是 0 → 看第 2 步用 pipefail + tee 把 stderr 记下来:
bashset -o pipefail export LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" 2> "$W/read.blockN.err" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" echo "EXIT=$?" tail -n +1 "$W/read.blockN.err" | head
你重点看:
EXIT= 如果不是 0,说明失败了read.blockN.err 里通常会直接写明原因(比如找不到文件、权限不够)因为这条命令本身会先创建输出文件:
bash> "$W/read.blockN.tsv"
只要上游一失败/没输出,你就会得到一个立刻出现但 0 字节的文件,看起来像“没跑”。
把下面两条的输出贴我一眼就能直接定案:
ls -lh "$W/reads.blocks.bed" 的输出head "$W/read.blockN.err"(或 cat 前几行)我再帮你把命令修到“一跑就成”的版本。
ls -lh "W/reads.blocks.bed"-rw-r--r-- 1 2024561579 life-gongl 22G Jan 22 10:30 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/reads.blocks.bed
(mulocdeep_cpy37) [2024561579@c54 zs]W/reads.blocks.bed"
Chr1A 209 239 SRR31004474.135722416/1/1 255 +
Chr1A 24338 24454 SRR31004474.135722416/1/1 255 +
(mulocdeep_cpy37) [2024561579@c54 zs]W/reads.blocks.bed"
396845813 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/reads.blocks.bed
你这个输入文件完全没问题:reads.blocks.bed 22G、3.968 亿行,所以这一步本来就会很久(而且会吃很多内存)。
你现在真正的问题是:你又把命令写成了续行模式(截图里有 >),还把结尾多打了东西,导致 shell 在等你把命令补完/命令结构被你写乱了——所以你并没有真正把那条“pipefail+errlog”的命令完整跑起来。
> 续行在那个窗口按一次:
确保回到正常提示符(不是 >)。
>)这条会:
read.blockN.errpipefail 保证上游失败能反映出来sortbashset -o pipefail; export LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; \ awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" 2> "$W/read.blockN.err" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"; \ echo "EXIT=$?"; ls -lh "$W/read.blockN.tsv" "$W/read.blockN.err"; head -n 5 "$W/read.blockN.err"
因为这个任务很大(4e8 行),正常跑时会表现为:
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort --parallel' | grep -v grep
bashls -lh "$W/read.blockN.tsv"
你这句 awk:
bash{c[$4]++} END{for(r in c) print r,c[r]}
会把 所有不同的 read name(第4列) 都存进内存里。你这数据量下可能需要几十 GB 内存,内存不够就会非常慢甚至被系统杀掉(OOM),然后你就会得到 0 字节输出或很小输出。
如果你跑完发现 read.blockN.err 里有类似 Killed / Out of memory,那就不是“没跑”,而是被杀了——我可以给你一个不吃内存的等价写法(sort+group by 流式计数),更稳。
你现在先按上面“一行命令”重新跑一次,然后把这两样贴回来我就能 100% 判断到底发生了什么:
1)echo EXIT=$? 的输出(那条命令会打印)
2)head -n 20 "$W/read.blockN.err"
你现在这个现象(ps 里看不到 awk/sort,同时 read.blockN.tsv 还是 0 字节)= 那条 awk|sort 根本没在跑,或者 一启动就立刻失败退出(最常见是 awk 程序写法/引号问题、或者直接被秒杀/报错)。
你截图里还有一个关键点:你前面尝试过用 双引号 包 awk 程序(awk "BEGIN{FS=OFS="\t"} ..."),那种写法会把引号搞炸,通常会直接报错然后退出;另外你现在也没把 stderr 打出来,所以看不到原因。
下面别猜了,直接用“强制把错误打出来 + 保证不进入 >”的一行命令跑一次,马上就能看到失败原因。
(你已经验证过了,我就不重复)
注意:awk 程序必须用单引号;这是你之前最容易写错的点。
bashset -o pipefail; export LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ 2> "$W/read.blockN.err" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"; echo "EXIT=$?"; wc -c "$W/read.blockN.tsv" "$W/read.blockN.err"; head -n 20 "$W/read.blockN.err"
EXIT=0 还是非 0read.blockN.err 里到底报了什么错把 head -n 20 "$W/read.blockN.err" 的内容贴我,我就能精确定位。
那就不是语法问题,而是:你这个 awk 会把 所有 read name 的哈希表放内存里。你的 reads.blocks.bed 有 3.97 亿行,这一步很可能需要几十 GB 内存,内存不够就会被系统秒杀,然后输出 0 字节。
这种情况下要换成不吃内存的等价写法:用排序+分组计数(流式),不需要在 awk 里存巨大 hash:
bashset -o pipefail; export LC_ALL=C; T=30; TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR"; cut -f4 "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
这个版本:
c[$4]++ 的意图一致(按第4列 read id 计数)你现在先跑第 2 步那条“一行带 errlog”的命令,然后把 EXIT 和 read.blockN.err 前 20 行发我,我就能告诉你到底是 语法/引号 还是 OOM/被杀,以及下一步该用哪个版本。
你不能在这个命令本身加上上查看进度或者实时工美工作的提示吗
能,而且可以在命令本身里加“实时进度/工作状态提示”,不靠另一个窗口 👍
但要说清楚一个现实限制:你现在这一步有 3.97 亿行,且核心瓶颈是 awk 的聚合,所以“进度”的定义只能是输入读到哪了,而不是“已经算完多少 key”。
下面我给你 3 种可行方案,从最稳妥到最直观,你选一个用就行。
pv 显示“已读行数 / ETA”这是最可靠、最直观的方式,直接嵌进命令本身。
reads.blocks.bed 是一行一个 blockpv -l -s <总行数> → 就能显示百分比、速度、ETAbashTOTAL_LINES=396845813 # 你刚刚 wc -l 算出来的 set -o pipefail export LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
终端会持续刷新,比如:
52.3M 13% [====> ] 1.2M/s ETA 2:45:10
这表示:
👉 这已经是你这个任务能做到的“最准进度条”
即使进度条在走,这一步后半段可能会明显变慢,因为:
{c[$4]++} 里不断扩张 hash进度条“慢下来”≠ 卡死。
如果你想更安心,可以加一个“心跳提示”:
bashTOTAL_LINES=396845813 set -o pipefail export LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | awk 'BEGIN{FS=OFS="\t"}{c[$4]++; if(NR%10000000==0) print "processed",NR > "/dev/stderr"} END{for(r in c) print r,c[r]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
这样 stderr 会每处理 1000 万行打印一次:
textprocessed 10000000 processed 20000000 ...
👉 你能明确看到 awk 还在活着
我必须直说一句工程真话:
你现在这条 awk 写法,在 4 亿行数据上是“危险的”
内存占用极大,很容易慢到像挂住,甚至被 OOM kill
bashTOTAL_LINES=396845813 set -o pipefail export LC_ALL=C T=30 TMPDIR="${W}/tmp.sort.$$"; mkdir -p "$TMPDIR" pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
👉 如果这是正式分析流程,我强烈建议你用这个版本
pv 是正解)如果你愿意,我可以帮你把整段 pipeline(从 BAM → blocks → gene → intron)统一改成“可监控 + 并行 + 不爆内存”的版本,一次性帮你收官。
对,你现在这条 pv+sort 并行版跟你原来的这条在结果上是等价的(输出的 read_id<TAB>blockN 一样),只是:
pv:显示读取进度(不改变数据)sort 用了 --parallel=$T -T $TMPDIR:更快(不改变排序规则本质,仍按第1列排序)也就是说你现在跑出来的 $W/read.blockN.tsv,和你原来那条得到的文件应该内容一致、顺序一致(按第1列字典序)。
你原始的是:
bashawk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' "$W/reads.blocks.bed" \ | sort -k1,1 > "$W/read.blockN.tsv"
你现在跑的是(截图里):
bashpv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | awk 'BEGIN{FS=OFS="\t"}{c[$4]++} END{for(r in c) print r,c[r]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv"
pv 只是把输入原样吐出去;sort --parallel/-T 只是 sort 的性能参数;逻辑不变。
你现在这条 awk {c[$4]++} 会把所有 readID 的计数表都放内存里。你这个文件 3.968 亿行,read 数量巨大,可能会非常吃内存,有时会慢到像挂住,甚至被系统 kill(OOM)。
如果你后面发现突然停了、或者 read.blockN.tsv 产出很小/为0,优先怀疑这个。
(不过你目前 pv 在跑,说明至少还没挂。)
跑完以后对比一下:
bashls -lh "$W/read.blockN.tsv"
bashhead -n 3 "$W/read.blockN.tsv"
bashr=$(cut -f1 "$W/read.blockN.tsv" | shuf -n 1) echo "$r" grep -P "^(\S+\t\S+\t\S+\t${r}\t)" "$W/reads.blocks.bed" | wc -l grep -P "^${r}\t" "$W/read.blockN.tsv"
两边的数字应该一致(前者是 blocks 行数,后者是你统计的 blockN)。
如果你希望我把这一段进一步升级成**“不吃内存、同样有 pv 进度条、还更快”**的等价实现(推荐在你这种 4e8 行场景用),我也可以直接给你替换版。
从你这张图看:pv 已经把输入文件读完了(100%),也就是
reads.blocks.bed 的 3.968 亿行已经全部喂进管道了 ✅但你还没回到 shell 提示符,说明后面还在做事,常见有两种情况:
END{for(r in c) ...} 把内存里的哈希表全部吐出来(这一步可能也要一阵子)所以现在的状态是:读完了,但“计算+排序”还没完成,不是卡住。
1)看 read.blockN.tsv 是否在增长
bashls -lh "$W/read.blockN.tsv"
隔 10–20 秒再跑一次。如果 size 在涨,就说明还在写/排序中。
2)看 sort/awk 进程是否还在
bashps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort --parallel' | grep -v grep
awk / sort:正常在跑3)看临时目录里有没有 sort 的临时文件
bashls -lh "$TMPDIR" | head
如果里面很多临时文件,说明 sort 在干活。
你 pv 的总行数设的是 396,845,813,但 pv 显示到 396M 就 100% ——这通常没问题(显示单位会四舍五入),但也可能意味着你给的 -s 不是最新值。想确认就再算一次:
bashwc -l "$W/reads.blocks.bed"
如果你把下面两条输出贴一下,我就能直接判断它现在处在 “awk 收尾” 还是 “sort 合并”:
ls -lh "$W/read.blockN.tsv"ps -u $USER -o pid,stat,etime,pcpu,cmd | grep -E 'awk|sort --parallel' | grep -v grep他在运行着我怎么在当前窗口输入呢?那然后我在新开的窗口输入又没有反应
你现在这个窗口里那条管道在前台跑着,所以当前窗口没法再输入别的命令(除非你中断它)。想“边跑边看状态”,要用下面几种办法之一:
在新窗口里直接用这些(尽量别用你之前那个复杂 grep):
pgrep 找进程bashpgrep -afu "$USER" 'pv|awk|sort'
ps 看最近的 pv/awk/sortbashps -u "$USER" -o pid,stat,etime,pcpu,cmd --sort=-etime | head -n 30
如果你还是“看不到”,最常见原因是:
✅ 解决:在新窗口先确认是不是同一个节点
bashhostname
必须和跑任务的那个窗口一样(你截图里是 c54)。如果不一样,你就 ssh c54(或按你们集群方式切到同一节点)再查。
两种常用操作:
在正在跑的窗口按:
Ctrl+Z然后输入:
bashbg
这样任务会在后台继续跑,你当前窗口就能继续敲命令了。
查看后台任务:
bashjobs -l
需要把它切回前台:
bashfg %1
注意:
Ctrl+Z是“暂停”,bg是“后台继续”。这对你这种长管道很好用。
你这次已经跑起来了,就先用 B1 最简单。
新窗口里(同节点)反复看这个:
bashls -lh "$W/read.blockN.tsv"
如果文件大小在涨,就肯定在干活(通常是 awk END 在吐、或 sort 在写/合并)。
也可以看 sort 临时文件:
bashls -lh "$TMPDIR" | head
99% 是因为新窗口不在同一节点。你按这个顺序排查就行:
bashhostname
hostname(你知道是 c54)bashssh c54
bashpgrep -afu "$USER" 'pv|awk|sort'
如果你告诉我:你们是 slurm 之类提交的作业,还是直接在登录节点跑的?我可以给你一句最稳的“在哪个节点跑就去哪看”的查法(比如用 squeue/scontrol 找节点)。
切到后台后怎么做
好,现在说最实用、一步一步的做法。你已经在前台跑着了,对吧?👇
Ctrl + Z
你会看到类似:
[1]+ Stopped pv -l -s ...
这一步只是暂停,还没继续跑。
紧接着输入:
bashbg
你会看到:
[1]+ pv -l -s ... &
✅ 到这一步:
bashjobs -l
示例输出:
[1]+ 123456 Running pv -l -s ... &
[1] 是 job ID123456 是进程 PIDbashps -p 123456 -o pid,stat,etime,pcpu,cmd
或者更简单:
bashpgrep -afu "$USER" 'pv|awk|sort'
你这个任务最靠谱的“进度”就是看输出文件有没有在变大:
bashls -lh "$W/read.blockN.tsv"
隔 10–30 秒看一次:
随时可以:
bashfg %1
(%1 对应 jobs 里那个 [1])
你会重新看到 pv 的进度条在刷。
现在这个 job 还是绑定在当前终端的,如果你担心断线:
bashdisown %1
这样:
⚠️ 但 disown 后你就不能再 fg 它了,只能靠 ps / ls -lh 看状态。
| 操作 | 命令 |
|---|---|
| 暂停当前任务 | Ctrl+Z |
| 后台继续跑 | bg |
| 看后台任务 | jobs -l |
| 切回前台 | fg %1 |
END{} 吐结果👉 最稳判断一句话:
只要 read.blockN.tsv 还在变大,它就一定在干活。
如果你愿意,下一步我可以帮你把后面那一整段(intersect / join / uniq)也统一改成「可后台 + 有进度 + 不爆内存」的一套模板,你这条线已经走到 80% 了 👍
(mulocdeep_cpy37) [2024561579@c54 zs] TOTAL_LINES=396845813 # 你刚刚 wc -l 算出来的
(mulocdeep_cpy37) [2024561579@c54 zs]
(mulocdeep_cpy37) [2024561579@c54 zs] export LC_ALL=C
(mulocdeep_cpy37) [2024561579@c54 zs] TMPDIR="{W}/tmp.sort.$$"; mkdir -p "TMPDIR"
(mulocdeep_cpy37) [2024561579@c54 zs] pv -l -s "W/reads.blocks.bed" \
| awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}' \ | sort --parallel="T" -T "$TMPDIR" -k1,1 \
"TOTAL_LINES" "W/reads.blocks.bed" | awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}' | sort --parallel="TMPDIR" -k1,1 > " bg
[1]+ pv -l -s "W/reads.blocks.bed" | awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}' | sort --parallel="T" -T "W/read.blockN.tsv" &
(mulocdeep_cpy37) [2024561579@c54 zs]TOTAL_LINES" "W/reads.blocks.bed" 3498142 Running | awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}'
3498143 Running | sort --parallel="TMPDIR" -k1,1 > "W/read.blockN.tsv" & (mulocdeep_cpy37) [2024561579@c54 zs] ps -p 123456 -o pid,stat,etime,pcpu,cmd
PID STAT ELAPSED %CPU CMD
(mulocdeep_cpy37) [2024561579@c54 zs]$ ps -p 123456 -o pid,stat,etime,pcpu,cmd
PID STAT ELAPSED %CPU CMD
你操作得完全正确 ✅(Ctrl+Z → bg → jobs -l 这一套很标准)
你现在看到这个:
bashps -p 123456 ...
没输出的原因很简单:
👉 123456 这个 PID 根本不是你的任务 PID,所以当然查不到任何东西。
你真正的 PID 在 jobs -l 里已经给出来了:
pv 的 PID:3498141awk 的 PID:3498142sort 的 PID:3498143bashps -p 3498141,3498142,3498143 -o pid,stat,etime,pcpu,cmd
或者分别看:
bashps -p 3498142 -o pid,stat,etime,pcpu,cmd # awk ps -p 3498143 -o pid,stat,etime,pcpu,cmd # sort
你刚才 pv 已经显示 100% 了,说明:
✅ reads.blocks.bed 已经全部读完
接下来一般是:
awk 在做 END{for(r in c) ...} 的收尾输出sort 在做最终排序/合并写盘所以它还会继续跑一段时间,这是正常的。
看输出文件有没有变大:
bashls -lh "$W/read.blockN.tsv"
隔 20 秒再看一次:
bashls -lh "$W/read.blockN.tsv"
只要 size 在涨,就说明还在写结果。
bashfg %1
如果你把这条的输出贴我一下:
bashps -p 3498141,3498142,3498143 -o pid,stat,etime,pcpu,cmd
我可以直接告诉你:现在是在 awk 收尾还是 sort 合并,以及大概还要等多久。
] ps -p 123456 -o pid,stat,etime,pcpu,cmd
PID STAT ELAPSED %CPU CMD
(mulocdeep_cpy37) [2024561579@c54 zs] ps -p 3498141,3498142,3498143 -o pid,stat,etime,pcpu,cmd
PID STAT ELAPSED %CPU CMD
3498142 S 18:34 36.5 awk BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}
3498143 R 18:34 4.2 sort --parallel=30 -T /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/tmp.sort.2161580 -k1,1
(mulocdeep_cpy37) [2024561579@c54 zs]W/read.blockN.tsv"
-rw-r--r-- 1 2024561579 life-gongl 0 Jan 22 10:54 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/read.blockN.tsv
(mulocdeep_cpy37) [2024561579@c54 zs]W/read.blockN.tsv"
-rw-r--r-- 1 2024561579 life-gongl 0 Jan 22 10:54 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/read.blockN.tsv
你这个状态是正常的,而且从你贴的输出能看出它确实还在干活。
read.blockN.tsv 还是 0 字节)你管道最后是:
bash... | sort ... > "$W/read.blockN.tsv"
sort 的行为是:先把所有输入都读完(必要时写很多临时文件到 TMPDIR),最后才一次性把最终排序结果写到输出文件。
所以在 sort 没完成之前:
read.blockN.tsv 很可能一直是 0(这是正常现象)TMPDIR 里的 sort 临时文件同时你这行:
awk 现在在 S(sleep)并不代表卡死,很多时候是在等 I/O/内存/管道;sort 在 R,说明它正在跑。另外你 ps 里没有 pv 了也正常:pv 读完就退出了(它 100% 后就完成使命)。
你现在的 TMPDIR 是:
.../tmp.sort.2161580
直接跑:
bashls -lh /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/tmp.sort.2161580 | head du -sh /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/tmp.sort.2161580
隔 30 秒再跑一次。如果 du -sh 在变大/临时文件在变多,就是在正常排序。
bashps -p 3498142,3498143 -o pid,stat,etime,pcpu,rss,vsz,cmd
bashdf -h "$W"
pv 100%:输入文件已经全部读完 ✅sort 还在运行:它在用 TMPDIR 做外排/合并 ✅read.blockN.tsv 还是 0:sort 还没到“最终输出”阶段 ✅read.blockN.tsv 才会开始变大?一般是 sort 进入最后合并并写最终结果时,输出文件才会明显增长;在此之前它可能一直是 0。
如果你把下面两次(间隔 30-60 秒)的输出贴一下,我就能更明确判断它是在“稳步推进”还是被 I/O/空间卡住:
du -sh "$TMPDIR"(两次)df -h "$W"(一次)那他在后台跑,跑完会有提示吗?
好问题,这里给你一个明确、不会误解的答案 👇
👉 会有提示,但前提是:你没有 disown 它,而且当前 shell 还活着。
你现在是用 Ctrl+Z → bg 放到后台的,这是“作业控制(job control)”模式,bash 会在它结束时提示你。
当这个后台任务真正结束时,当前窗口会自动打印一行提示,类似下面之一:
text[1]+ Done pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" | awk ... | sort ...
或者(如果中间出错):
text[1]+ Exit 1 pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" | awk ... | sort ...
这就是 “跑完了”的官方信号 ✅
不需要你盯着、不需要你手动查。
你现在跑:
bashjobs -l
只要看到:
text[1]+ Running ...
👉 就说明还在跑
等它跑完,再跑一次 jobs -l:
Done只有下面几种情况 你才收不到提示:
bashdisown %1
👉 bash 不再管这个 job(常用于防 SSH 断线)
👉 作业还在跑,但这个 shell 没了,当然不会提示
nohup / screen / tmux 启的👉 提示会在对应的会话里,不在当前 shell
你现在 都不是这些情况,所以你会看到提示。
即使你错过了那行 Done,也没关系,用这两个就够:
bashjobs -l
bashls -lh "$W/read.blockN.tsv"
下次跑这种超长任务,直接用:
bashnohup bash -c '你的整条命令' > run.log 2>&1 &
然后你只需要:
bashtail -f run.log
SSH 断了也不怕。
是的,你现在这个后台任务跑完后,bash 会明确告诉你
[1]+ Done ...
你不用一直盯着。
如果你愿意,等这个跑完,我可以帮你 检查输出是否完整、是否合理,或者直接帮你把后续 pipeline 全部改成同样“后台可控 + 可监控”的形式。
然后我现在在跑一个样本,他就这么慢,那我剩下的是不是可以把剩下的30个样本都整合起来,放到lenovo lico 网页端,提交作业让他自动分配跑,我只需要提供bash命令行命令就行,设置好参数和需要的线程数
可以的,而且非常适合用“批量脚本 + job array(作业数组)”这种方式:你把每个样本要跑的 bash 命令写成一个脚本,平台(Lenovo LiCO/网页端)负责把 30 个样本分配到不同节点/队列并行跑,你只需要设好 线程数 / 内存 / 临时目录 / 日志。
下面给你一套直接能用的模板(你只要准备样本列表和每个样本的 BAM 路径/输出目录规则)。
比如 samples.tsv(两列:sample_id 和 bam_path):
tsvSRR31004474 /datapool/home/2024561579/zs/star/SRR31004474/SRR31004474_Aligned.sortedByCoord.out.bam SRR31004475 /path/to/SRR31004475.bam ...
这个脚本只负责跑一个样本,线程数从调度器环境变量里取(如果没有就默认 30)。
我把你最慢那步(read.blockN.tsv)写成带 pv 进度条 + 30 核 sort;并且我推荐你用“sort+uniq 计数版”(更稳,不容易爆内存),结果等价于
c[$4]++。
bash#!/usr/bin/env bash set -euo pipefail SAMPLE="$1" BAM="$2" # 资源参数:调度器给多少核就用多少核;否则默认30 T="${SLURM_CPUS_PER_TASK:-30}" EXCL="${EXCL:-2308}" # 输出目录:按你的习惯 W="/datapool/home/2024561579/zs/internal_readlevel/per_sample/${SAMPLE}" mkdir -p "$W" cd "$W" export LC_ALL=C TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" echo "[INFO] sample=$SAMPLE threads=$T" echo "[INFO] bam=$BAM" echo "[INFO] W=$W" # ---- 1) BAM -> blocks(R1/R2)你原来的两段(可加samtools线程;bedtools单线程) samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed" samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > "$W/reads.blocks.r2.bed" cat "$W/reads.blocks.r1.bed" "$W/reads.blocks.r2.bed" > "$W/reads.blocks.bed" # ---- 2) 统计每个read有多少block:强烈建议用 sort+uniq(更稳),并加pv进度 TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") echo "[INFO] reads.blocks.bed lines=$TOTAL_LINES" # 等价于:awk {c[$4]++} END{for(r in c) print r,c[r]} | sort -k1,1 # 但不会把巨大hash塞内存,且sort能并行 pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" echo "[INFO] done read.blockN.tsv size=$(du -h "$W/read.blockN.tsv" | awk '{print $1}')" # 清临时目录(可选) rm -rf "$TMPDIR" echo "[INFO] sample $SAMPLE DONE"
你后面那段
bedtools intersect/join/...也完全可以继续写在这个脚本里;只是你现在最痛的就是read.blockN.tsv这步,我先把它工程化地稳住。
在 LiCO 网页端一般有“提交作业/脚本”界面,本质上就是跑一段 scheduler 脚本。你如果是 Slurm 风格,通常是这样(最常见):
bash#!/usr/bin/env bash #SBATCH -J intron_pipe #SBATCH -c 30 #SBATCH --mem=120G #SBATCH -t 2-00:00:00 #SBATCH -o logs/%x_%A_%a.out #SBATCH -e logs/%x_%A_%a.err #SBATCH --array=1-30 set -euo pipefail mkdir -p logs # 取第 SLURM_ARRAY_TASK_ID 行 LINE=$(sed -n "${SLURM_ARRAY_TASK_ID}p" samples.tsv) SAMPLE=$(echo "$LINE" | awk '{print $1}') BAM=$(echo "$LINE" | awk '{print $2}') bash run_one_sample.sh "$SAMPLE" "$BAM"
提交:
bashsbatch submit_array.sh
如果 LiCO 网页端是“填命令行”,你就把 sbatch submit_array.sh 作为提交命令,或者直接上传/粘贴这个脚本内容。
awk c[$4]++ 在 4e8 行上非常容易内存爆炸/超级慢;我上面给的 sort+uniq 版本通常更稳、更能吃满 30 核(主要在 sort)。bedtools bamtobed 单线程,所以 BAM→blocks 这步即使用 30 核,真正加速主要来自 samtools -@ 的解压/读取;别指望线性提速。-T "$TMPDIR" 很重要:放在你写权限足、空间足的地方。如果你把“剩下那段(intersect/join)”也要一起塞进作业数组,我可以按同样风格把它继续接在 run_one_sample.sh 里,并且把其中的 sort 全部改成 --parallel=$T -T $TMPDIR,同时加上你喜欢的 pv/心跳日志,让每个样本的日志都能实时判断跑到哪一步了。
这个先让他跑着,网页端提交不是这种脚本,需要的是一行一行的命令行,然后是
#!/usr/bin/env bash
#SBATCH -J intron_pipe
#SBATCH -c 30
#SBATCH --mem=120G
#SBATCH -t 2-00:00:00
#SBATCH -o logs/%x_%A_%a.out
#SBATCH -e logs/%x_%A_%a.err
#SBATCH --array=1-30
set -euo pipefail
mkdir -p logs
LINE={SLURM_ARRAY_TASK_ID}p" samples.tsv)
SAMPLE=LINE" | awk '{print 1}')
BAM=(echo "LINE" | awk '{print 2}')
bash run_one_sample.sh "BAM"这个风格的
但是前面sbatch这些都会自动生成,不需要我们写,我们就写正常的处理命令行和参数线程数就好,我不知道我表达清楚没有
我懂你的意思了,而且你表达得很清楚 👍
你们 Lenovo LiCO 网页端 的逻辑是:
#SBATCH 这些头所以我们要做的是:
👉 把“array 逻辑”写成纯 bash,而不是 sbatch 脚本
下面我给你一个 100% 符合你这个网页端模式、可以直接粘进去用的版本。
几乎所有网页端 array / 批量任务,都会提供一个任务编号变量,常见名字之一:
$TASK_ID$JOB_INDEX$ARRAY_INDEX$LSB_JOBINDEX$PBS_ARRAY_INDEX👉 你只需要确认 LiCO 用的是哪一个
(不知道也没关系,我下面给你“自动兜底”的写法)
bashset -euo pipefail export LC_ALL=C # ---------- 1. 取 array index(自动兼容多种平台) ---------- IDX="${TASK_ID:-${JOB_INDEX:-${ARRAY_INDEX:-${LSB_JOBINDEX:-${PBS_ARRAY_INDEX:-1}}}}}" echo "[INFO] task index = $IDX" # ---------- 2. 从 samples.tsv 取这一行 ---------- LINE=$(sed -n "${IDX}p" samples.tsv) SAMPLE=$(echo "$LINE" | awk '{print $1}') BAM=$(echo "$LINE" | awk '{print $2}') echo "[INFO] SAMPLE=$SAMPLE" echo "[INFO] BAM=$BAM" # ---------- 3. 线程数(网页端给 30 核,这里直接用 30) ---------- T=30 EXCL=2308 # ---------- 4. 输出目录 ---------- W="/datapool/home/2024561579/zs/internal_readlevel/per_sample/${SAMPLE}" mkdir -p "$W" cd "$W" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" # ---------- 5. BAM -> blocks ---------- samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > reads.blocks.r1.bed samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > reads.blocks.r2.bed cat reads.blocks.r1.bed reads.blocks.r2.bed > reads.blocks.bed # ---------- 6. 统计 read.blockN(稳健版 + 并行) ---------- TOTAL_LINES=$(wc -l < reads.blocks.bed) echo "[INFO] blocks lines = $TOTAL_LINES" pv -l -s "$TOTAL_LINES" reads.blocks.bed \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > read.blockN.tsv echo "[INFO] DONE sample $SAMPLE"
👉 这一整段就是你网页端要填的“命令行内容”
👉 不需要 sbatch
👉 不需要 #!/usr/bin/env bash(有也不影响)
tsvSRR31004474 /datapool/home/2024561579/zs/star/SRR31004474/SRR31004474_Aligned.sortedByCoord.out.bam SRR31004475 /path/to/SRR31004475.bam ...
samples.tsv 所在目录你现在看到的是:
而这个方式是:
这是标准 HPC 正解,你现在走的是完全对的方向。
bedtools intersect / join / intron 无缝接进这个命令IDX= 那行简化成一行你已经把最难、最容易踩坑的那一步跑通了,剩下的都是工程整合问题了 👍
我不知道你要哪个tsv,有什么用处,我所有的bam文件都有固定的路径变量设置
BAMS=(star/*/*Aligned.sortedByCoord.out.bam)
懂了:你不需要单独准备 samples.tsv,完全可以在命令里用你这个 glob:
bashBAMS=(star/*/*Aligned.sortedByCoord.out.bam)
samples.tsv 的作用只是“把第 N 个任务对应到第 N 个样本”,现在我们直接用 BAMS 数组来做这个映射就行。
下面给你一段可以直接粘到 LiCO 网页端的“纯命令行”(不含 #SBATCH),它会:
IDXIDX 选第 IDX 个 BAMread.blockN.tsv30⚠️ 重点:array 下标通常从 1 开始;bash 数组从 0 开始,所以要
IDX-1。
bashset -euo pipefail export LC_ALL=C # 1) 取 array index(尽量兼容不同平台变量名;你们LiCO是哪一个都行) IDX="${TASK_ID:-${JOB_INDEX:-${ARRAY_INDEX:-${LSB_JOBINDEX:-${PBS_ARRAY_INDEX:-1}}}}}" echo "[INFO] task index (1-based) = $IDX" # 2) 线程数 T=30 EXCL=2308 # 3) 收集 BAM 列表(建议加 sort,保证每次顺序一致) mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort) N=${#BAMS[@]} echo "[INFO] found BAMs: $N" if [ "$IDX" -lt 1 ] || [ "$IDX" -gt "$N" ]; then echo "[ERROR] IDX=$IDX out of range 1..$N" >&2 exit 2 fi BAM="${BAMS[$((IDX-1))]}" echo "[INFO] BAM=$BAM" # 4) 解析 sample 名(按你的路径:star/SAMPLE/xxx_Aligned...bam) # 如果你的目录层级不一样,改这一行就行 SAMPLE=$(basename "$(dirname "$BAM")") echo "[INFO] SAMPLE=$SAMPLE" # 5) 输出目录 W="/datapool/home/2024561579/zs/internal_readlevel/per_sample/${SAMPLE}" mkdir -p "$W" cd "$W" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" # 6) BAM -> blocks(R1 / R2) samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > reads.blocks.r1.bed samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > reads.blocks.r2.bed cat reads.blocks.r1.bed reads.blocks.r2.bed > reads.blocks.bed # 7) 统计 read.blockN.tsv(稳健版:sort+uniq,避免巨大awk哈希) TOTAL_LINES=$(wc -l < reads.blocks.bed) echo "[INFO] reads.blocks.bed lines=$TOTAL_LINES" pv -l -s "$TOTAL_LINES" reads.blocks.bed \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > read.blockN.tsv echo "[INFO] DONE sample $SAMPLE"
N(你的 BAM 数量)
ls -1 star/*/*Aligned.sortedByCoord.out.bam | wc -lstar/ 这个目录(否则 glob 找不到 BAM)你之前写的 BAMS=(star/*/*Aligned.sortedByCoord.out.bam) 在 bash 里也能用,但顺序不保证每次都一样(尤其不同机器/文件系统)。我上面用 ls | sort 固定顺序,避免 array 任务“今天跑 A 明天跑 B”。
如果你把 ls -1 star/*/*Aligned.sortedByCoord.out.bam | head 的前几行贴一下,我还能顺手确认 SAMPLE=$(basename "$(dirname "$BAM")") 这一行取样本名是不是正好符合你想要的目录命名。
for BAM in (basename "PER/W"
echo "== $SAMPLE =="
samtools view -b -F BAM"
| bedtools bamtobed -split -i -
| awk 'BEGIN{FS=OFS="\t"}{4"/1";print}' > "$W/reads.blocks.r1.bed"
samtools view -b -F BAM"
| bedtools bamtobed -split -i -
| awk 'BEGIN{FS=OFS="\t"}{4"/2";print}' > "$W/reads.blocks.r2.bed"
cat "W/reads.blocks.r2.bed" > "$W/reads.blocks.bed"
awk 'BEGIN{FS=OFS="\t"}{c[4]++} END{for(r in c) print r,c[r]}' "W/reads.blocks.bed"
| sort -k1,1 > "$W/read.blockN.tsv"
bedtools intersect -a "INTRON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' | sort -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort -k1,1 -k2,2 > "W/read_gene.intronContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' | sort -k1,1 -k2,2 > "W/intron.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/intron.internal.cand.read_gene.tsv"
| sort -k2,2 -k1,1 > "$W/intron.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' "W/intron.internal.unique.read_gene.tsv"
| sort -k1,1 > "$W/intron_internal_by_gene.tsv"
bedtools intersect -a "EXON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' | sort -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort -k1,1 -k2,2 > "W/read_gene.exonContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' | sort -k1,1 -k2,2 > "W/exon.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/exon.internal.cand.read_gene.tsv"
| sort -k2,2 -k1,1 > "$W/exon.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' "W/exon.internal.unique.read_gene.tsv"
| sort -k1,1 > "$W/exon_internal_by_gene.tsv"
done
这个是原来,本身要用的批量跑的模板,不过是在终端界面不是王爷后台,我希望你check一下,你的逻辑和任务跟这个有没有冲突
你这份 for 循环模板的逻辑是自洽的,和我前面给你的“并行/进度条/多核 sort”思路不冲突;我给你的改动本质上是:
samtools view 解压、sort)吃满核pv 进度条awk {c[$4]++}(超吃内存)换成 等价但更稳的 sort+uniq(推荐)下面我按你的脚本逐段帮你做“冲突检查 + 必要的修正点”。
for BAM in $BAMS; do 这个写法有风险如果你前面是 BAMS=(star/*/*Aligned.sortedByCoord.out.bam),那么正确遍历是:
bashfor BAM in "${BAMS[@]}"; do
你现在的 for BAM in $BAMS 会把数组当成一个字符串展开,遇到空格/特殊字符会炸(bam 路径一般没空格,但规范上还是改掉)。
SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") 这一句会得到啥?它会得到 文件名去掉后缀,例如:
SRR31004474_Aligned.sortedByCoord.out.bam -> SRR31004474
✅ 这跟你现在的目录结构是兼容的(你 W=SAMPLE)。
但如果你的 bam 文件名前缀不是“纯 sample”,例如 SRR31004474_foo_Aligned...bam,那 SAMPLE 也会是 SRR31004474_foo。这不算冲突,只要你 PER 目录期望这样命名即可。
samtools view -b ... | bedtools ... 建议改成 -u(更快,不改变语义)你现在用 -b 会让 samtools 重新压缩 BAM,管道里反而慢。
改成 -u(未压缩 BAM)通常明显快,结果完全等价:
bashsamtools view -u ...
并且加线程:
bashsamtools view -@ "$T" -u ...
read.blockN.tsv 这步:你现在的 awk hash 会非常吃内存这一步就是你之前“一个样本跑很久”的根源之一。逻辑没问题,但工程上很容易 OOM/极慢。
等价更稳(我推荐替换):
read\tcount就是我建议把这句:
bashawk '{c[$4]++} END{for(r in c) print r,c[r]}' reads.blocks.bed | sort
替换成等价的 sort+uniq 流式写法(为了避免 hash 爆内存、并让多核 sort 真正起作用)。
除此之外,你后面 intron/exon 的 intersect/join/awk 逻辑 完全可以保留,只是把所有 sort 统一加上 --parallel=$T -T $TMPDIR 会更快。
你拿这个当作“网页端单任务的一条命令块”或终端脚本都行;我只改性能和健壮性:
bashset -euo pipefail export LC_ALL=C T=30 EXCL=2308 # 你的变量:PER / INTRON / EXON / BAMS 需要在前面已定义 # BAMS=(star/*/*Aligned.sortedByCoord.out.bam) for BAM in "${BAMS[@]}"; do SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") W=$PER/$SAMPLE mkdir -p "$W" echo "== $SAMPLE ==" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" # blocks (更快:-@ + -u) samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed" samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > "$W/reads.blocks.r2.bed" cat "$W/reads.blocks.r1.bed" "$W/reads.blocks.r2.bed" > "$W/reads.blocks.bed" # read.blockN.tsv(等价但更稳、更能用多核) TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" # intron internal(只加速 sort) bedtools intersect -a "$W/reads.blocks.bed" -b "$INTRON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.intronContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/intron.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/intron.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/intron.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/intron.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/intron_internal_by_gene.tsv" # exon internal(同理) bedtools intersect -a "$W/reads.blocks.bed" -b "$EXON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.exonContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/exon.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/exon.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/exon.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/exon.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/exon_internal_by_gene.tsv" rm -rf "$TMPDIR" done
read.blockN.tsv 的统计方式(但结果等价)samtools/sort 的性能参数 + pv 进度条网页端如果会给你一个“任务编号 IDX”,你就把 for BAM in ... 改成“只取一个 BAM”即可(每个任务一个样本)。这一步我可以按你们 LiCO 的变量名给你一版“单样本命令块”。
你现在只需要告诉我 LiCO 给的 array index 变量名是什么(一般网页端会显示),比如是 $TASK_ID 还是别的。
如果你不想查,我也可以继续用我之前那行“多变量兜底”的写法。
是的,还在跑,而且跑得很“正常”。
你这张图里给出的信息已经足够判断了。
你多次执行 jobs -l,结果始终是:
text[1]+ 3498141 Running pv ... 3498142 Running awk ... 3498143 Running sort ...
这三点说明问题:
Running(不是 Done / Stopped)awk 和 sort 进程都还在所以结论非常明确:
👉 它还在后台执行中,没有卡死,也没有结束。
结合你之前的信息:
pv 已经 100%reads.blocks.bed 已经全部读完awk 还在 RunningEND{for(r in c) ...},把巨大的 hash 表往下游吐sort 也在 Running这是你这个任务里最慢、最容易让人误判“卡住”的阶段,但它是正常阶段。
read.blockN.tsv 还是 0 字节?因为:
bash... | sort ... > read.blockN.tsv
sort 的行为是:
所有输入处理完 → 排序完成 → 最后一步才一次性写输出文件
所以在 sort 结束之前:
read.blockN.tsv 很可能一直是 0$TMPDIR 里的临时文件这是 GNU sort 的标准行为,不是异常。
比 jobs 还靠谱的是看 sort 临时目录:
bashdu -sh "$TMPDIR"
隔 30–60 秒再跑一次:
bashdu -sh "$TMPDIR"
因为你是用 Ctrl+Z → bg 放到后台的(没有 disown),所以:
👉 跑完后,当前窗口会自动出现提示,类似:
[1]+ Done pv -l -s ... | awk ... | sort ...
这是 bash 的 job control 提示,你不会错过。
你现在做的所有判断、操作都是完全正确的。
等这一单跑完,再把它搬到网页端 array 跑 30 个样本,是非常正确的工程决策。
#!/bin/bash
#SBATCH --job-name='Common_Job_01141034'
#SBATCH --chdir=/datapool/home/2024561579/zs
#SBATCH --partition=life-gongl
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=60
#SBATCH --time=0-24:00
echo job start time is date
echo hostname
T=30
EXCL=2308
for BAM in "(basename "PER/W"
echo "== $SAMPLE =="
TMPDIR="{W}/tmp.sort.$$" mkdir -p "TMPDIR"
samtools view -@ "EXCL" -f 64 "BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{4=4"/1";print}' > "W/reads.blocks.r1.bed"
samtools view -@ "EXCL" -f 128 "BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{4=4"/2";print}' > "W/reads.blocks.r2.bed"
cat "W/reads.blocks.r2.bed" > "$W/reads.blocks.bed"
TOTAL_LINES=W/reads.blocks.bed")
pv -l -s "W/reads.blocks.bed"
| cut -f4
| sort --parallel="TMPDIR"
| uniq -c
| awk 'BEGIN{OFS="\t"}{print 1}'
| sort --parallel="TMPDIR" -k1,1 \
"$W/read.blockN.tsv"
bedtools intersect -a "INTRON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' \
| sort --parallel="T" -T "TMPDIR" -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/read_gene.intronContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' \ | sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/intron.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/intron.internal.cand.read_gene.tsv"
| sort --parallel="TMPDIR" -k2,2 -k1,1 \
"$W/intron.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' \
"W/intron.internal.unique.read_gene.tsv"
| sort --parallel="TMPDIR" -k1,1 \
"$W/intron_internal_by_gene.tsv"
bedtools intersect -a "EXON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' \
| sort --parallel="T" -T "TMPDIR" -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/read_gene.exonContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' \ | sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/exon.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/exon.internal.cand.read_gene.tsv"
| sort --parallel="TMPDIR" -k2,2 -k1,1 \
"$W/exon.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' \
"W/exon.internal.unique.read_gene.tsv"
| sort --parallel="TMPDIR" -k1,1 \
"$W/exon_internal_by_gene.tsv"
rm -rf "$TMPDIR"
done
echo job end time is date
这个是提交在网页段的,根据你的代码他自动生成的完整版,但是现在还在排队,没啥问题吧
整体看起来没啥大问题,能跑起来;但我建议你在它还排队的时候,立刻改/确认下面几处,否则很容易出现“跑了但什么都没做”或“资源申请不匹配导致慢/排更久”。
BAMS / PER / INTRON / EXON 现在并没有定义你脚本只有注释:
bash# 你的变量:PER / INTRON / EXON / BAMS 需要在前面已定义 # BAMS=(star/*/*Aligned.sortedByCoord.out.bam)
如果网页端环境没有替你提前 export 这些变量,那脚本会出现两种情况:
BAMS 为空 → for BAM in "${BAMS[@]}" 循环不会执行 → 作业很快结束(看起来“啥也没跑”)PER/INTRON/EXON 为空 → 中间命令找不到文件/写到奇怪目录 → 直接报错退出✅ 建议你在脚本开头显式加上(按你自己的真实路径改):
bashset -euo pipefail export LC_ALL=C PER=/datapool/home/2024561579/zs/internal_readlevel/per_sample INTRON=/path/to/intron.bed EXON=/path/to/exon.bed BAMS=(star/*/*Aligned.sortedByCoord.out.bam)
并加一个 sanity check(很有用):
bashecho "[INFO] BAM count: ${#BAMS[@]}" ls -l "${BAMS[0]}" || true
T=30,但 Slurm 写的是 --ntasks-per-node=60你这份头:
bash#SBATCH --nodes=1 #SBATCH --ntasks-per-node=60
但你实际是单个脚本、单机内多线程(samtools -@ 30、sort --parallel=30),更符合的写法通常是 --cpus-per-task=30(或网页端等价选项)。
现在这样不一定跑不了,但可能造成:
✅ 如果网页端能选“CPU cores = 30”,那脚本里最好与之匹配(让平台生成 cpus-per-task 风格)。至少你要确保网页端那边的核数/任务数设置跟 T=30一致。
你现在是:
bashfor BAM in "${BAMS[@]}"; do ... done
这意味着一个 job 从头跑到尾,30 个样本会串行。
如果你想“30 个样本自动分配并行”,应该用网页端的 array(每个 task 跑 1 个 BAM),而不是在一个 job 里 for 循环。
你现在这份脚本适合:
如果你的目标是“30 个样本并行”,需要把 for 换成“按 task index 取一个 BAM”。我可以按你们 LiCO 的 task id 变量名给你改成 array 版。
read.blockN.tsv 计算方式 OK(而且比原 awk hash 稳)这一段是我之前推荐的“稳健版”,没冲突:
bashpv ... | cut -f4 | sort | uniq -c | awk ... | sort > read.blockN.tsv
✅ 逻辑等价于你原来的 c[$4]++,但更不容易 OOM。
PER / INTRON / EXON / BAMS 的定义(强烈建议写死在脚本里)T=30(别让它生成 60 tasks 那种配置)如果你回我一句:网页端这个作业是“数组/并行”还是“单作业”,以及 LiCO 提供的 task index 变量名,我就能把脚本改到完全符合你们平台的最佳实践(不排更久、不浪费核、自动并行)。
我网页端申请了 60个核心,你说得对好像确实没有设置,他不知道那些东西是什么在哪里,帮我加上,谢谢
下面我把你这份脚本补齐必须的变量定义,并顺手做了几处不会改变逻辑、但能避免“空变量啥也没跑/写到奇怪路径”的防呆。你只要把 INTRON/EXON 的真实路径改成你自己的即可(如果你已经有固定路径,也直接填上)。
另外你网页端申请了 60 核:我这里把 T 改成默认用 60(也支持你手动改成 30)。如果你希望每个样本只用 30 核,就把 T=60 改回 T=30。
bash#!/bin/bash #SBATCH --job-name='Common_Job_01141034' #SBATCH --chdir=/datapool/home/2024561579/zs #SBATCH --partition=life-gongl #SBATCH --nodes=1 #SBATCH --ntasks-per-node=60 #SBATCH --time=0-24:00 set -euo pipefail export LC_ALL=C echo job start time is "$(date)" echo "$(hostname)" # ========= 你必须明确设置的变量 ========= # 1) 输出根目录(你之前 PER/$SAMPLE) PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" # 2) 注释文件(请改成你真实的 intron / exon bed 路径) INTRON="/datapool/home/2024561579/zs/path/to/INTRON.bed" EXON="/datapool/home/2024561579/zs/path/to/EXON.bed" # 3) BAM 列表(固定路径规则) # 用 sort 固定顺序,避免不同环境 glob 顺序不一致 mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort) # 4) 线程数:你网页端申请 60 核,这里就用 60(想用 30 就改成 30) T=60 # 5) 过滤 flag EXCL=2308 # ========= 防呆检查(强烈建议保留) ========= echo "[INFO] PER=$PER" echo "[INFO] INTRON=$INTRON" echo "[INFO] EXON=$EXON" echo "[INFO] threads(T)=$T" echo "[INFO] BAM count=${#BAMS[@]}" if [ "${#BAMS[@]}" -eq 0 ]; then echo "[ERROR] No BAMs found: star/*/*Aligned.sortedByCoord.out.bam" >&2 exit 2 fi if [ ! -s "$INTRON" ]; then echo "[ERROR] INTRON bed not found or empty: $INTRON" >&2 exit 2 fi if [ ! -s "$EXON" ]; then echo "[ERROR] EXON bed not found or empty: $EXON" >&2 exit 2 fi mkdir -p "$PER" # ========= 主循环 ========= for BAM in "${BAMS[@]}"; do SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") W="$PER/$SAMPLE" mkdir -p "$W" echo "== $SAMPLE ==" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" # blocks(-u 更快;-@ 用线程) samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed" samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > "$W/reads.blocks.r2.bed" cat "$W/reads.blocks.r1.bed" "$W/reads.blocks.r2.bed" > "$W/reads.blocks.bed" # read.blockN.tsv(等价但更稳;并行 sort;带 pv 进度) TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") echo "[INFO] $SAMPLE blocks lines=$TOTAL_LINES" pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" # intron internal bedtools intersect -a "$W/reads.blocks.bed" -b "$INTRON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.intronContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/intron.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/intron.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/intron.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/intron.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/intron_internal_by_gene.tsv" # exon internal bedtools intersect -a "$W/reads.blocks.bed" -b "$EXON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.exonContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/exon.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/exon.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/exon.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/exon.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/exon_internal_by_gene.tsv" rm -rf "$TMPDIR" done echo job end time is "$(date)"
把这两行替换成你真实文件路径即可:
bashINTRON="/datapool/home/2024561579/zs/path/to/INTRON.bed" EXON="/datapool/home/2024561579/zs/path/to/EXON.bed"
如果你把你实际的 INTRON / EXON 路径发我,我可以直接帮你填成最终版。
你现在申请 60 核,但脚本里 T=60 才能用满;如果你想用 30 核跑,就把 T=30,但那样会“申请 60 实用 30”。(能跑,只是不划算。)
如果你后面想把 30 个样本并行跑(而不是一个 job 串行跑完全部),我们再把这份改成“array 每个 task 跑 1 个 BAM”的纯 bash 版本就行。
INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed"
EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed"
把前面那些我不需要的给我删掉!,我给你强调跑过了!,我不想一遍遍的说!
明白了,我收敛,不再来回强调你已经跑过的东西。直接给结论 + 给你要的精简修改。
“他这个是跑完了一个任务了吗?”
没有。
你现在看到的是:
text[1]+ 3498141 Running pv ... 3498142 Running awk ... 3498143 Running sort ...
这表示的是:
如果跑完了一个任务,你会看到其中之一:
text[1]+ Done pv ... | awk ... | sort ...
或者 jobs -l 里 直接没有这个 job 了。
👉 现在这种状态 = 这个样本的 read.blockN.tsv 这一步还没结束
(前面已经解释过:sort 在最后阶段才一次性写文件)
你现在明确的是:
bashINTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed"
这两个路径完全 OK,没有任何问题。
下面这一段,你可以直接替换脚本最上面的变量定义部分,没有多余解释、没有占位、没有废话:
bashset -euo pipefail export LC_ALL=C PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed" mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort) T=60 EXCL=2308
就这些。
等它结束,你会明确看到 Done,不是靠猜。
你这一步我不会再重复解释了。
等它 真的 Done,你再问下一步(比如:是否该 kill / 是否该改成 array / 是否该拆样本并行),我直接给决策,不兜圈子。
现在这一步跑完了。
你最后一屏已经变成:
pv 那行显示 Donejobs -l 已经回到提示符(前台空闲了)这表示这条后台 pipeline(pv | awk | sort > read.blockN.tsv)结束了;pv 作为 job leader Done 了,整个 job 也结束了。
现在你做这三件事确认结果就行(只要 10 秒):
bashls -lh "$W/read.blockN.tsv" head -n 3 "$W/read.blockN.tsv" tail -n 3 "$W/read.blockN.tsv"
如果文件不是 0 字节、且 head 能看到 readID<TAB>数字,那就彻底 OK。
想更硬核一点验证“统计没错”,随机抽一个 read 比对一下:
bashr=$(cut -f1 "$W/read.blockN.tsv" | shuf -n 1) echo "check $r" grep -F $'\t'"$r"$'\t' "$W/reads.blocks.bed" | wc -l grep -P "^${r}\t" "$W/read.blockN.tsv"
接下来你就可以继续跑后面的 intron/exon 那几段了(或者把整套搬到网页端批量)。
ls -lh "W/read.blockN.tsv"-rw-r--r-- 1 2024561579 life-gongl 6.8G Jan 22 11:37 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/read.blockN.tsv
(mulocdeep_cpy37) [2024561579@c54 zs]W/read.blockN.tsv"
SRR31004474.1/1/1 1
SRR31004474.1/2/2 1
SRR31004474.10/1/1 1
(mulocdeep_cpy37) [2024561579@c54 zs]W/read.blockN.tsv"
SRR31004474.99999998/2/2 1
SRR31004474.99999999/1/1 1
SRR31004474.99999999/2/2 2
OK,这一步完全正常、结果也对:
read.blockN.tsv 已经生成,6.8G,说明不是空跑。head/tail 都是 readID<TAB>block数 的格式,符合预期。.../2/2 2 说明确实有些 read(那条 read 的那一端)被 split 成了 2 个 blocks,这也符合 bamtobed -split 的行为。如果你想再快速做一个“自洽性检查”(可选,但很快):
bash# 抽一个 read,核对 blocks.bed 里出现次数 == read.blockN.tsv 的计数 r=$(cut -f1 "$W/read.blockN.tsv" | shuf -n 1) echo "CHECK $r" n1=$(awk -v r="$r" 'BEGIN{FS="\t"} $4==r {c++} END{print c+0}' "$W/reads.blocks.bed") n2=$(awk -v r="$r" 'BEGIN{FS="\t"} $1==r {print $2; exit}' "$W/read.blockN.tsv") echo "blocks.bed=$n1 read.blockN.tsv=$n2"
接下来你就可以继续跑你后面的 intron internal / exon internal 那几段了(它们会比这一步快得多,除非 intersect 命中特别多)。
PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample"
INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed"
EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed"
mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort)
T=60
EXCL=2308
T=58
EXCL=2308
for BAM in "(basename "PER/W"
echo "== $SAMPLE =="
TMPDIR="{W}/tmp.sort.$$" mkdir -p "TMPDIR"
samtools view -@ "EXCL" -f 64 "BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{4=4"/1";print}' > "W/reads.blocks.r1.bed"
samtools view -@ "EXCL" -f 128 "BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{4=4"/2";print}' > "W/reads.blocks.r2.bed"
cat "W/reads.blocks.r2.bed" > "$W/reads.blocks.bed"
TOTAL_LINES=W/reads.blocks.bed")
pv -l -s "W/reads.blocks.bed"
| cut -f4
| sort --parallel="TMPDIR"
| uniq -c
| awk 'BEGIN{OFS="\t"}{print 1}'
| sort --parallel="TMPDIR" -k1,1 \
"$W/read.blockN.tsv"
bedtools intersect -a "INTRON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' \
| sort --parallel="T" -T "TMPDIR" -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/read_gene.intronContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' \ | sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/intron.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/intron.internal.cand.read_gene.tsv"
| sort --parallel="TMPDIR" -k2,2 -k1,1 \
"$W/intron.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' \
"W/intron.internal.unique.read_gene.tsv"
| sort --parallel="TMPDIR" -k1,1 \
"$W/intron_internal_by_gene.tsv"
bedtools intersect -a "EXON" -f 1.0 -wa -wb
| awk 'BEGIN{FS=OFS="\t"}{print 10,2,3}' \
| sort --parallel="T" -T "TMPDIR" -u \
| awk 'BEGIN{FS=OFS="\t"}{k=1"\t"2; c[k]++} END{for(k in c) print k,c[k]}' \
| sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/read_gene.exonContainedBlockN.tsv"
join -t W/read.blockN.tsv" "W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' \ | sort --parallel="T" -T "$TMPDIR" -k1,1 -k2,2 \
"$W/exon.internal.cand.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{cnt[1]=2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \
"W/exon.internal.cand.read_gene.tsv"
| sort --parallel="TMPDIR" -k2,2 -k1,1 \
"$W/exon.internal.unique.read_gene.tsv"
awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' \
"W/exon.internal.unique.read_gene.tsv"
| sort --parallel="TMPDIR" -k1,1 \
"$W/exon_internal_by_gene.tsv"
rm -rf "$TMPDIR"
done
现在这个脚本没问题了吧,没问题我提交了
这个脚本逻辑上没问题,可以提交。不过我建议你在提交前做 3 个非常小的“收尾修正”,避免踩坑(都不改变算法)。
T 你写了两次(60 又 58)现在实际生效的是最后一次 T=58。建议只留一个,避免以后自己看懵:
bashT=58 EXCL=2308
set -euo pipefail(防止中间某步失败还继续往下跑)你现在没加,某个 bedtools/samtools 出错时,脚本可能继续跑并生成一堆“看起来有文件但其实错”的结果。放在最开头:
bashset -euo pipefail export LC_ALL=C
for BAM in "${BAMS[@]}" 是对的,但 BAMS 用 ls 时如果没有匹配会报错你现在是:
bashmapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort)
如果某次路径没匹配,ls 会直接让整个脚本失败(其实这也挺好)。更稳一点、错误更明确的话,可以加一句 BAM 数量检查(不想加也行):
bash[ "${#BAMS[@]}" -gt 0 ] || { echo "No BAMs found under star/*/*Aligned.sortedByCoord.out.bam" >&2; exit 2; }
(只做上面 3 点 + 去掉重复 T,不加多余检查的话也行)
bashset -euo pipefail export LC_ALL=C PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed" mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort) T=58 EXCL=2308 for BAM in "${BAMS[@]}"; do SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") W=$PER/$SAMPLE mkdir -p "$W" echo "== $SAMPLE ==" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed" samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > "$W/reads.blocks.r2.bed" cat "$W/reads.blocks.r1.bed" "$W/reads.blocks.r2.bed" > "$W/reads.blocks.bed" TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" bedtools intersect -a "$W/reads.blocks.bed" -b "$INTRON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.intronContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/intron.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/intron.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/intron.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/intron.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/intron_internal_by_gene.tsv" bedtools intersect -a "$W/reads.blocks.bed" -b "$EXON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.exonContainedBlockN.tsv" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/exon.internal.cand.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/exon.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/exon.internal.unique.read_gene.tsv" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/exon.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/exon_internal_by_gene.tsv" rm -rf "$TMPDIR" done
你要是就按你原样提交也大概率能跑,但我建议至少把 重复的 T 删掉 + 加 set -euo pipefail。
如果你提交的是“单作业串行跑所有样本”,这版就对;如果你想“30 个样本并行”,那就得改成 array 取单个 BAM(你一句话我就给你改)。
wc -l " join -t W/read.blockN.tsv" "W/read_gene.intronContainedBlockN.tsv" | awk 'BEGIN{FS=OFS="\t"}{if(4==1,3}' | sort -k1,1 -k2,2 > "W/intron.internal.cand.read_gene.tsv"
(mulocdeep_cpy37) [2024561579@c54 zs] awk 'BEGIN{FS=OFS="\t"}{cnt[1]++; g[2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' "W/intron.internal.unique.read_gene.tsv"
(mulocdeep_cpy37) [2024561579@c54 zs] awk 'BEGIN{FS=OFS="\t"}{c[2]++} END{for(g in c) print g,c[g]}' "W/intron_internal_by_gene.tsv"
n 5 " wc -l "W/intron_internal_by_gene.tsv"
39109 /datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474/intron_internal_by_gene.tsv
TraesCS1A03G0000200 2464
TraesCS1A03G0000400 478
TraesCS1A03G0000600 90
TraesCS1A03G0000800 318
TraesCS1A03G0001000 41
(mulocdeep_cpy37) [2024561579@c54 zs]$ 这个是正常输出了对吧
是的,这是完全正常、而且逻辑上自洽的输出 ✅
我按你的每一步结果给你快速对照解释一下,你可以放心继续往下跑。
read_gene.intronContainedBlockN.tsvbashwc -l read_gene.intronContainedBlockN.tsv 4688294
这一步的含义是:
数量在 几百万级 是非常正常的,因为:
✅ 正常
intron.internal.cand.read_gene.tsvbashjoin read.blockN.tsv read_gene.intronContainedBlockN.tsv \ | awk '{if($4==$2) print $1,$3}'
逻辑检查(非常关键):
$2 = 该 read 的 总 block 数$4 = 该 read 在 intron 中 被完全包含的 block 数if($4==$2) ⇒ 这个 read 的所有 block 都在 intron 里👉 这一步筛出来的就是:
“完全 intron-internal 的 reads(但可能一个 read 对多个 gene)”
这是你设计里最重要的一道过滤,逻辑完全正确。
intron.internal.unique.read_gene.tsvbashcnt[$1] == 1
含义:
这是标准的 unique mapping 处理方式。
✅ 正常,而且是严谨的
intron_internal_by_gene.tsv(最终统计)bashwc -l intron_internal_by_gene.tsv 39109
textTraesCS1A03G0000200 2464 TraesCS1A03G0000400 478 ...
解释:
gene_id read_count在小麦这种基因组规模下:
这一步已经是最终可用结果了。
👉 这是标准、正确、可直接用于下游分析的结果。
你现在可以 放心对剩余样本批量跑(不管是网页端 array 还是串行),这个模板是已经被“实测验证”的了。
如果你下一步是想:
你直接说目标,我在你现有输出格式上帮你接下一步,不改前面任何东西。
你那个网页脚本还是一跑起来就结束了
最常见原因就两个(都会导致“启动立刻结束”):
/datapool/home/2024561579/zs,所以 star/*/*Aligned.sortedByCoord.out.bam 根本匹配不到任何文件BAMS 为空,for BAM in "${BAMS[@]}" 就什么都不做,于是秒退你现在别猜,直接把这几行塞到网页脚本最前面(在 for 之前),它会强制切目录 + 打印 BAM 数量,并且如果找不到 BAM 会明确报错而不是悄悄结束。
bashset -euo pipefail export LC_ALL=C cd /datapool/home/2024561579/zs echo "[INFO] pwd=$(pwd)" echo "[INFO] hostname=$(hostname)" # 让 glob 没匹配时变成空数组(否则有的环境会把星号原样保留) shopt -s nullglob PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed" # 不用 ls/mapfile,直接用 glob 数组:最稳 BAMS=(star/*/*Aligned.sortedByCoord.out.bam) echo "[INFO] BAM count=${#BAMS[@]}" if [ "${#BAMS[@]}" -eq 0 ]; then echo "[ERROR] No BAMs matched: star/*/*Aligned.sortedByCoord.out.bam" >&2 echo "[ERROR] check that 'star/' exists under $(pwd)" >&2 ls -la exit 2 fi T=58 EXCL=2308
然后你原来的:
bashfor BAM in "${BAMS[@]}"; do ... done
保持不变。
mapfile -t BAMS < <(ls -1 star/*/*Aligned.sortedByCoord.out.bam | sort)ls/mapfile 得到空/报错,就会很容易“什么都没跑”或者直接退出。BAMS=(star/*/*Aligned.sortedByCoord.out.bam) + cd + BAM count,能把问题一次性钉死。你把网页作业输出里这两行贴我一下就行(不用别的):
[INFO] pwd=...[INFO] BAM count=...我就能立刻判断是 目录不对 还是 匹配规则不对。
你设置个带日志地被,比如开是哪一步就说快速,那一步出问题结束了,就报告,然后给我生成完整版
下面给你一份带“步骤日志 + 出错立刻报哪一步 + 自动写 log 文件”的完整版脚本。它会做到:
cd 到你的工作目录,检查 BAM 数量/注释文件是否存在你直接把它粘到网页端提交的脚本里就行。
bash#!/bin/bash set -euo pipefail export LC_ALL=C ############################ # 基本路径与参数(按你给的) ############################ cd /datapool/home/2024561579/zs PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed" T=58 EXCL=2308 ############################ # 日志设置 ############################ RUN_TAG="$(date +%Y%m%d_%H%M%S)" LOGDIR="/datapool/home/2024561579/zs/internal_readlevel/logs_${RUN_TAG}" mkdir -p "$LOGDIR" MASTER_LOG="$LOGDIR/master.log" exec > >(tee -a "$MASTER_LOG") 2>&1 log() { echo "[$(date '+%F %T')] $*"; } # 记录当前正在跑哪个样本/步骤,便于报错 CUR_SAMPLE="(none)" CUR_STEP="(init)" on_err() { local exit_code=$? log "[ERROR] exit_code=$exit_code sample=$CUR_SAMPLE step=$CUR_STEP line=$1" log "[ERROR] last_command: $2" log "[ERROR] see master log: $MASTER_LOG" exit $exit_code } trap 'on_err $LINENO "$BASH_COMMAND"' ERR run_step() { # 用法:run_step "STEP_NAME" command... CUR_STEP="$1"; shift log "[STEP] sample=$CUR_SAMPLE step=$CUR_STEP START" "$@" log "[STEP] sample=$CUR_SAMPLE step=$CUR_STEP DONE" } ############################ # 防“秒退”检查 ############################ log "[INFO] pwd=$(pwd)" log "[INFO] hostname=$(hostname)" log "[INFO] threads(T)=$T EXCL=$EXCL" log "[INFO] INTRON=$INTRON" log "[INFO] EXON=$EXON" log "[INFO] LOGDIR=$LOGDIR" [ -s "$INTRON" ] || { log "[FATAL] INTRON not found/empty: $INTRON"; exit 2; } [ -s "$EXON" ] || { log "[FATAL] EXON not found/empty: $EXON"; exit 2; } # glob 没匹配时变空数组(否则可能把星号当字符串导致奇怪行为) shopt -s nullglob BAMS=(star/*/*Aligned.sortedByCoord.out.bam) log "[INFO] BAM count=${#BAMS[@]}" if [ "${#BAMS[@]}" -eq 0 ]; then log "[FATAL] No BAMs matched: star/*/*Aligned.sortedByCoord.out.bam" log "[FATAL] listing current dir:" ls -la exit 2 fi mkdir -p "$PER" ############################ # 主循环(逐样本) ############################ for BAM in "${BAMS[@]}"; do CUR_SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") W="$PER/$CUR_SAMPLE" mkdir -p "$W" SAMPLE_LOG="$LOGDIR/${CUR_SAMPLE}.log" log "[SAMPLE] ===== START $CUR_SAMPLE =====" log "[SAMPLE] bam=$BAM" log "[SAMPLE] outdir=$W" log "[SAMPLE] sample_log=$SAMPLE_LOG" # 把样本内的输出也 tee 进 sample log # (用子 shell 包住,避免影响主流程) ( exec > >(tee -a "$SAMPLE_LOG") 2>&1 TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" # 1) blocks run_step "make_blocks_r1" bash -lc \ "samtools view -@ '$T' -u -F '$EXCL' -f 64 '$BAM' \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS=\"\t\"}{\$4=\$4\"/1\";print}' > '$W/reads.blocks.r1.bed'" run_step "make_blocks_r2" bash -lc \ "samtools view -@ '$T' -u -F '$EXCL' -f 128 '$BAM' \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS=\"\t\"}{\$4=\$4\"/2\";print}' > '$W/reads.blocks.r2.bed'" run_step "concat_blocks" bash -lc \ "cat '$W/reads.blocks.r1.bed' '$W/reads.blocks.r2.bed' > '$W/reads.blocks.bed'" # 2) read.blockN.tsv(稳健版 + pv) TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") log "[INFO] blocks lines=$TOTAL_LINES" run_step "make_read_blockN" bash -lc \ "pv -l -s '$TOTAL_LINES' '$W/reads.blocks.bed' \ | cut -f4 \ | sort --parallel='$T' -T '$TMPDIR' \ | uniq -c \ | awk 'BEGIN{OFS=\"\t\"}{print \$2,\$1}' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 \ > '$W/read.blockN.tsv'" # 3) intron internal run_step "intron_intersect_to_read_gene_blockN" bash -lc \ "bedtools intersect -a '$W/reads.blocks.bed' -b '$INTRON' -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS=\"\t\"}{print \$4,\$10,\$1,\$2,\$3}' \ | sort --parallel='$T' -T '$TMPDIR' -u \ | awk 'BEGIN{FS=OFS=\"\t\"}{k=\$1\"\t\"\$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 -k2,2 \ > '$W/read_gene.intronContainedBlockN.tsv'" run_step "intron_join_filter" bash -lc \ "join -t \$'\t' -1 1 -2 1 '$W/read.blockN.tsv' '$W/read_gene.intronContainedBlockN.tsv' \ | awk 'BEGIN{FS=OFS=\"\t\"}{if(\$4==\$2) print \$1,\$3}' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 -k2,2 \ > '$W/intron.internal.cand.read_gene.tsv'" run_step "intron_unique_read_gene" bash -lc \ "awk 'BEGIN{FS=OFS=\"\t\"}{cnt[\$1]++; g[\$1]=\$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ '$W/intron.internal.cand.read_gene.tsv' \ | sort --parallel='$T' -T '$TMPDIR' -k2,2 -k1,1 \ > '$W/intron.internal.unique.read_gene.tsv'" run_step "intron_by_gene" bash -lc \ "awk 'BEGIN{FS=OFS=\"\t\"}{c[\$2]++} END{for(g in c) print g,c[g]}' \ '$W/intron.internal.unique.read_gene.tsv' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 \ > '$W/intron_internal_by_gene.tsv'" # 4) exon internal run_step "exon_intersect_to_read_gene_blockN" bash -lc \ "bedtools intersect -a '$W/reads.blocks.bed' -b '$EXON' -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS=\"\t\"}{print \$4,\$10,\$1,\$2,\$3}' \ | sort --parallel='$T' -T '$TMPDIR' -u \ | awk 'BEGIN{FS=OFS=\"\t\"}{k=\$1\"\t\"\$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 -k2,2 \ > '$W/read_gene.exonContainedBlockN.tsv'" run_step "exon_join_filter" bash -lc \ "join -t \$'\t' -1 1 -2 1 '$W/read.blockN.tsv' '$W/read_gene.exonContainedBlockN.tsv' \ | awk 'BEGIN{FS=OFS=\"\t\"}{if(\$4==\$2) print \$1,\$3}' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 -k2,2 \ > '$W/exon.internal.cand.read_gene.tsv'" run_step "exon_unique_read_gene" bash -lc \ "awk 'BEGIN{FS=OFS=\"\t\"}{cnt[\$1]++; g[\$1]=\$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ '$W/exon.internal.cand.read_gene.tsv' \ | sort --parallel='$T' -T '$TMPDIR' -k2,2 -k1,1 \ > '$W/exon.internal.unique.read_gene.tsv'" run_step "exon_by_gene" bash -lc \ "awk 'BEGIN{FS=OFS=\"\t\"}{c[\$2]++} END{for(g in c) print g,c[g]}' \ '$W/exon.internal.unique.read_gene.tsv' \ | sort --parallel='$T' -T '$TMPDIR' -k1,1 \ > '$W/exon_internal_by_gene.tsv'" run_step "cleanup_tmp" rm -rf "$TMPDIR" log "[SAMPLE] ===== DONE $CUR_SAMPLE =====" ) done log "job end time is $(date)" log "[INFO] master log: $MASTER_LOG" log "[INFO] per-sample logs: $LOGDIR/*.log"
[SAMPLE] ===== START SRR... =====[STEP] ... START / DONE[ERROR] sample=... step=... line=...[ERROR] last_command: ...logs_时间戳/SRRxxxx.log如果你网页端还是“秒退”,你把 master.log 的最后 30 行贴我(不用全贴),我可以直接告诉你是:
job start time is Thu Jan 22 14:39:35 CST 2026
[2026-01-22 14:39:35] [INFO] pwd=/datapool/home/2024561579/zs
[2026-01-22 14:39:35] [INFO] hostname=c58
[2026-01-22 14:39:35] [INFO] threads(T)=58 EXCL=2308
[2026-01-22 14:39:35] [INFO] INTRON=/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed
[2026-01-22 14:39:35] [INFO] EXON=/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed
[2026-01-22 14:39:35] [INFO] LOGDIR=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935
[2026-01-22 14:39:35] [INFO] BAM count=30
[2026-01-22 14:39:35] [SAMPLE] ===== START SRR31004474 =====
[2026-01-22 14:39:35] [SAMPLE] bam=star/SRR31004474/SRR31004474_Aligned.sortedByCoord.out.bam
[2026-01-22 14:39:35] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004474
[2026-01-22 14:39:35] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004474.log
[2026-01-22 14:39:35] [STEP] sample=SRR31004474 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:39:42] [STEP] sample=SRR31004474 step=make_blocks_r1 DONE
[2026-01-22 14:39:42] [STEP] sample=SRR31004474 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:39:48] [STEP] sample=SRR31004474 step=make_blocks_r2 DONE
[2026-01-22 14:39:48] [STEP] sample=SRR31004474 step=concat_blocks START
[2026-01-22 14:39:54] [STEP] sample=SRR31004474 step=concat_blocks DONE
[2026-01-22 14:39:54] [INFO] blocks lines=0
[2026-01-22 14:39:54] [STEP] sample=SRR31004474 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:40:00] [STEP] sample=SRR31004474 step=make_read_blockN DONE
[2026-01-22 14:40:00] [STEP] sample=SRR31004474 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:40:06] [STEP] sample=SRR31004474 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:40:06] [STEP] sample=SRR31004474 step=intron_join_filter START
[2026-01-22 14:40:12] [STEP] sample=SRR31004474 step=intron_join_filter DONE
[2026-01-22 14:40:12] [STEP] sample=SRR31004474 step=intron_unique_read_gene START
[2026-01-22 14:40:17] [STEP] sample=SRR31004474 step=intron_unique_read_gene DONE
[2026-01-22 14:40:17] [STEP] sample=SRR31004474 step=intron_by_gene START
[2026-01-22 14:40:23] [STEP] sample=SRR31004474 step=intron_by_gene DONE
[2026-01-22 14:40:23] [STEP] sample=SRR31004474 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:40:28] [STEP] sample=SRR31004474 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:40:28] [STEP] sample=SRR31004474 step=exon_join_filter START
[2026-01-22 14:40:34] [STEP] sample=SRR31004474 step=exon_join_filter DONE
[2026-01-22 14:40:34] [STEP] sample=SRR31004474 step=exon_unique_read_gene START
[2026-01-22 14:40:40] [STEP] sample=SRR31004474 step=exon_unique_read_gene DONE
[2026-01-22 14:40:40] [STEP] sample=SRR31004474 step=exon_by_gene START
[2026-01-22 14:40:46] [STEP] sample=SRR31004474 step=exon_by_gene DONE
[2026-01-22 14:40:46] [STEP] sample=SRR31004474 step=cleanup_tmp START
[2026-01-22 14:40:46] [STEP] sample=SRR31004474 step=cleanup_tmp DONE
[2026-01-22 14:40:46] [SAMPLE] ===== DONE SRR31004474 =====
[2026-01-22 14:40:46] [SAMPLE] ===== START SRR31004475 =====
[2026-01-22 14:40:46] [SAMPLE] bam=star/SRR31004475/SRR31004475_Aligned.sortedByCoord.out.bam
[2026-01-22 14:40:46] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004475
[2026-01-22 14:40:46] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004475.log
[2026-01-22 14:40:46] [STEP] sample=SRR31004475 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:40:52] [STEP] sample=SRR31004475 step=make_blocks_r1 DONE
[2026-01-22 14:40:52] [STEP] sample=SRR31004475 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:40:58] [STEP] sample=SRR31004475 step=make_blocks_r2 DONE
[2026-01-22 14:40:58] [STEP] sample=SRR31004475 step=concat_blocks START
[2026-01-22 14:41:04] [STEP] sample=SRR31004475 step=concat_blocks DONE
[2026-01-22 14:41:04] [INFO] blocks lines=0
[2026-01-22 14:41:04] [STEP] sample=SRR31004475 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:41:09] [STEP] sample=SRR31004475 step=make_read_blockN DONE
[2026-01-22 14:41:09] [STEP] sample=SRR31004475 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:41:15] [STEP] sample=SRR31004475 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:41:15] [STEP] sample=SRR31004475 step=intron_join_filter START
[2026-01-22 14:41:21] [STEP] sample=SRR31004475 step=intron_join_filter DONE
[2026-01-22 14:41:21] [STEP] sample=SRR31004475 step=intron_unique_read_gene START
[2026-01-22 14:41:27] [STEP] sample=SRR31004475 step=intron_unique_read_gene DONE
[2026-01-22 14:41:27] [STEP] sample=SRR31004475 step=intron_by_gene START
[2026-01-22 14:41:33] [STEP] sample=SRR31004475 step=intron_by_gene DONE
[2026-01-22 14:41:33] [STEP] sample=SRR31004475 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:41:39] [STEP] sample=SRR31004475 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:41:39] [STEP] sample=SRR31004475 step=exon_join_filter START
[2026-01-22 14:41:45] [STEP] sample=SRR31004475 step=exon_join_filter DONE
[2026-01-22 14:41:51] [STEP] sample=SRR31004475 step=exon_unique_read_gene DONE
[2026-01-22 14:41:51] [STEP] sample=SRR31004475 step=exon_by_gene START
[2026-01-22 14:41:57] [STEP] sample=SRR31004475 step=exon_by_gene DONE
[2026-01-22 14:41:57] [STEP] sample=SRR31004475 step=cleanup_tmp START
[2026-01-22 14:41:57] [STEP] sample=SRR31004475 step=cleanup_tmp DONE
[2026-01-22 14:41:57] [SAMPLE] ===== DONE SRR31004475 =====
[2026-01-22 14:41:57] [SAMPLE] ===== START SRR31004476 =====
[2026-01-22 14:41:57] [SAMPLE] bam=star/SRR31004476/SRR31004476_Aligned.sortedByCoord.out.bam
[2026-01-22 14:41:57] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004476
[2026-01-22 14:41:57] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004476.log
[2026-01-22 14:41:57] [STEP] sample=SRR31004476 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:42:04] [STEP] sample=SRR31004476 step=make_blocks_r1 DONE
[2026-01-22 14:42:04] [STEP] sample=SRR31004476 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:42:10] [STEP] sample=SRR31004476 step=make_blocks_r2 DONE
[2026-01-22 14:42:10] [STEP] sample=SRR31004476 step=concat_blocks START
[2026-01-22 14:42:16] [STEP] sample=SRR31004476 step=concat_blocks DONE
[2026-01-22 14:42:16] [STEP] sample=SRR31004476 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:42:22] [STEP] sample=SRR31004476 step=make_read_blockN DONE
[2026-01-22 14:42:22] [STEP] sample=SRR31004476 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:42:29] [STEP] sample=SRR31004476 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:42:29] [STEP] sample=SRR31004476 step=intron_join_filter START
[2026-01-22 14:42:35] [STEP] sample=SRR31004476 step=intron_join_filter DONE
[2026-01-22 14:42:35] [STEP] sample=SRR31004476 step=intron_unique_read_gene START
[2026-01-22 14:42:42] [STEP] sample=SRR31004476 step=intron_unique_read_gene DONE
[2026-01-22 14:42:42] [STEP] sample=SRR31004476 step=intron_by_gene START
[2026-01-22 14:42:48] [STEP] sample=SRR31004476 step=intron_by_gene DONE
bash: bedtools: command not found
[2026-01-22 14:42:55] [STEP] sample=SRR31004476 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:42:55] [STEP] sample=SRR31004476 step=exon_join_filter START
[2026-01-22 14:43:01] [STEP] sample=SRR31004476 step=exon_join_filter DONE
[2026-01-22 14:43:01] [STEP] sample=SRR31004476 step=exon_unique_read_gene START
[2026-01-22 14:43:07] [STEP] sample=SRR31004476 step=exon_unique_read_gene DONE
[2026-01-22 14:43:07] [STEP] sample=SRR31004476 step=exon_by_gene START
[2026-01-22 14:43:14] [STEP] sample=SRR31004476 step=exon_by_gene DONE
[2026-01-22 14:43:14] [STEP] sample=SRR31004476 step=cleanup_tmp START
[2026-01-22 14:43:14] [STEP] sample=SRR31004476 step=cleanup_tmp DONE
[2026-01-22 14:43:14] [SAMPLE] ===== DONE SRR31004476 =====
[2026-01-22 14:43:14] [SAMPLE] ===== START SRR31004477 =====
[2026-01-22 14:43:14] [SAMPLE] bam=star/SRR31004477/SRR31004477_Aligned.sortedByCoord.out.bam
[2026-01-22 14:43:14] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004477
[2026-01-22 14:43:14] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004477.log
[2026-01-22 14:43:14] [STEP] sample=SRR31004477 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:43:20] [STEP] sample=SRR31004477 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:43:26] [STEP] sample=SRR31004477 step=make_blocks_r2 DONE
[2026-01-22 14:43:26] [STEP] sample=SRR31004477 step=concat_blocks START
[2026-01-22 14:43:33] [STEP] sample=SRR31004477 step=concat_blocks DONE
[2026-01-22 14:43:33] [INFO] blocks lines=0
[2026-01-22 14:43:33] [STEP] sample=SRR31004477 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:43:39] [STEP] sample=SRR31004477 step=make_read_blockN DONE
[2026-01-22 14:43:39] [STEP] sample=SRR31004477 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:43:45] [STEP] sample=SRR31004477 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:43:45] [STEP] sample=SRR31004477 step=intron_join_filter START
[2026-01-22 14:43:52] [STEP] sample=SRR31004477 step=intron_join_filter DONE
[2026-01-22 14:43:58] [STEP] sample=SRR31004477 step=intron_unique_read_gene DONE
[2026-01-22 14:43:58] [STEP] sample=SRR31004477 step=intron_by_gene START
[2026-01-22 14:44:04] [STEP] sample=SRR31004477 step=intron_by_gene DONE
[2026-01-22 14:44:04] [STEP] sample=SRR31004477 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:44:10] [STEP] sample=SRR31004477 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:44:10] [STEP] sample=SRR31004477 step=exon_join_filter START
[2026-01-22 14:44:16] [STEP] sample=SRR31004477 step=exon_join_filter DONE
[2026-01-22 14:44:16] [STEP] sample=SRR31004477 step=exon_unique_read_gene START
[2026-01-22 14:44:23] [STEP] sample=SRR31004477 step=exon_unique_read_gene DONE
[2026-01-22 14:44:23] [STEP] sample=SRR31004477 step=exon_by_gene START
[2026-01-22 14:44:29] [STEP] sample=SRR31004477 step=exon_by_gene DONE
[2026-01-22 14:44:29] [STEP] sample=SRR31004477 step=cleanup_tmp START
[2026-01-22 14:44:29] [STEP] sample=SRR31004477 step=cleanup_tmp DONE
[2026-01-22 14:44:29] [SAMPLE] ===== DONE SRR31004477 =====
[2026-01-22 14:44:29] [SAMPLE] ===== START SRR31004479 =====
[2026-01-22 14:44:29] [SAMPLE] bam=star/SRR31004479/SRR31004479_Aligned.sortedByCoord.out.bam
[2026-01-22 14:44:29] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004479
[2026-01-22 14:44:29] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004479.log
[2026-01-22 14:44:29] [STEP] sample=SRR31004479 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:44:36] [STEP] sample=SRR31004479 step=make_blocks_r1 DONE
[2026-01-22 14:44:36] [STEP] sample=SRR31004479 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:44:42] [STEP] sample=SRR31004479 step=make_blocks_r2 DONE
[2026-01-22 14:44:42] [STEP] sample=SRR31004479 step=concat_blocks START
[2026-01-22 14:44:49] [STEP] sample=SRR31004479 step=concat_blocks DONE
[2026-01-22 14:44:49] [INFO] blocks lines=0
[2026-01-22 14:44:49] [STEP] sample=SRR31004479 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:44:55] [STEP] sample=SRR31004479 step=make_read_blockN DONE
[2026-01-22 14:44:55] [STEP] sample=SRR31004479 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:45:03] [STEP] sample=SRR31004479 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:45:03] [STEP] sample=SRR31004479 step=intron_join_filter START
[2026-01-22 14:45:09] [STEP] sample=SRR31004479 step=intron_join_filter DONE
[2026-01-22 14:45:09] [STEP] sample=SRR31004479 step=intron_unique_read_gene START
[2026-01-22 14:45:17] [STEP] sample=SRR31004479 step=intron_unique_read_gene DONE
[2026-01-22 14:45:17] [STEP] sample=SRR31004479 step=intron_by_gene START
[2026-01-22 14:45:23] [STEP] sample=SRR31004479 step=intron_by_gene DONE
[2026-01-22 14:45:23] [STEP] sample=SRR31004479 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:45:30] [STEP] sample=SRR31004479 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:45:30] [STEP] sample=SRR31004479 step=exon_join_filter START
[2026-01-22 14:45:37] [STEP] sample=SRR31004479 step=exon_join_filter DONE
[2026-01-22 14:45:37] [STEP] sample=SRR31004479 step=exon_unique_read_gene START
[2026-01-22 14:45:43] [STEP] sample=SRR31004479 step=exon_unique_read_gene DONE
[2026-01-22 14:45:50] [STEP] sample=SRR31004479 step=exon_by_gene DONE
[2026-01-22 14:45:50] [STEP] sample=SRR31004479 step=cleanup_tmp START
[2026-01-22 14:45:50] [STEP] sample=SRR31004479 step=cleanup_tmp DONE
[2026-01-22 14:45:50] [SAMPLE] ===== DONE SRR31004479 =====
[2026-01-22 14:45:50] [SAMPLE] ===== START SRR31004490 =====
[2026-01-22 14:45:50] [SAMPLE] bam=star/SRR31004490/SRR31004490_Aligned.sortedByCoord.out.bam
[2026-01-22 14:45:50] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004490
[2026-01-22 14:45:50] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004490.log
[2026-01-22 14:45:50] [STEP] sample=SRR31004490 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:45:56] [STEP] sample=SRR31004490 step=make_blocks_r1 DONE
[2026-01-22 14:45:56] [STEP] sample=SRR31004490 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:46:03] [STEP] sample=SRR31004490 step=make_blocks_r2 DONE
[2026-01-22 14:46:03] [STEP] sample=SRR31004490 step=concat_blocks START
[2026-01-22 14:46:09] [STEP] sample=SRR31004490 step=concat_blocks DONE
[2026-01-22 14:46:09] [INFO] blocks lines=0
[2026-01-22 14:46:09] [STEP] sample=SRR31004490 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:46:16] [STEP] sample=SRR31004490 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:46:22] [STEP] sample=SRR31004490 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:46:22] [STEP] sample=SRR31004490 step=intron_join_filter START
[2026-01-22 14:46:29] [STEP] sample=SRR31004490 step=intron_join_filter DONE
[2026-01-22 14:46:29] [STEP] sample=SRR31004490 step=intron_unique_read_gene START
[2026-01-22 14:46:35] [STEP] sample=SRR31004490 step=intron_unique_read_gene DONE
[2026-01-22 14:46:35] [STEP] sample=SRR31004490 step=intron_by_gene START
[2026-01-22 14:46:41] [STEP] sample=SRR31004490 step=intron_by_gene DONE
[2026-01-22 14:46:41] [STEP] sample=SRR31004490 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:46:48] [STEP] sample=SRR31004490 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:46:48] [STEP] sample=SRR31004490 step=exon_join_filter START
[2026-01-22 14:46:54] [STEP] sample=SRR31004490 step=exon_join_filter DONE
[2026-01-22 14:46:54] [STEP] sample=SRR31004490 step=exon_unique_read_gene START
[2026-01-22 14:47:01] [STEP] sample=SRR31004490 step=exon_unique_read_gene DONE
[2026-01-22 14:47:01] [STEP] sample=SRR31004490 step=exon_by_gene START
[2026-01-22 14:47:07] [STEP] sample=SRR31004490 step=exon_by_gene DONE
[2026-01-22 14:47:07] [STEP] sample=SRR31004490 step=cleanup_tmp START
[2026-01-22 14:47:07] [STEP] sample=SRR31004490 step=cleanup_tmp DONE
[2026-01-22 14:47:07] [SAMPLE] ===== DONE SRR31004490 =====
[2026-01-22 14:47:07] [SAMPLE] ===== START SRR31004501 =====
[2026-01-22 14:47:07] [SAMPLE] bam=star/SRR31004501/SRR31004501_Aligned.sortedByCoord.out.bam
[2026-01-22 14:47:07] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004501
[2026-01-22 14:47:07] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004501.log
[2026-01-22 14:47:07] [STEP] sample=SRR31004501 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:47:14] [STEP] sample=SRR31004501 step=make_blocks_r1 DONE
[2026-01-22 14:47:14] [STEP] sample=SRR31004501 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:47:20] [STEP] sample=SRR31004501 step=make_blocks_r2 DONE
[2026-01-22 14:47:20] [STEP] sample=SRR31004501 step=concat_blocks START
[2026-01-22 14:47:27] [STEP] sample=SRR31004501 step=concat_blocks DONE
[2026-01-22 14:47:27] [INFO] blocks lines=0
[2026-01-22 14:47:27] [STEP] sample=SRR31004501 step=make_read_blockN START
bash: pv: command not found
[2026-01-22 14:47:33] [STEP] sample=SRR31004501 step=make_read_blockN DONE
[2026-01-22 14:47:33] [STEP] sample=SRR31004501 step=intron_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:47:40] [STEP] sample=SRR31004501 step=intron_intersect_to_read_gene_blockN DONE
[2026-01-22 14:47:40] [STEP] sample=SRR31004501 step=intron_join_filter START
[2026-01-22 14:47:46] [STEP] sample=SRR31004501 step=intron_join_filter DONE
[2026-01-22 14:47:46] [STEP] sample=SRR31004501 step=intron_unique_read_gene START
[2026-01-22 14:47:53] [STEP] sample=SRR31004501 step=intron_unique_read_gene DONE
[2026-01-22 14:47:59] [STEP] sample=SRR31004501 step=intron_by_gene DONE
[2026-01-22 14:47:59] [STEP] sample=SRR31004501 step=exon_intersect_to_read_gene_blockN START
bash: bedtools: command not found
[2026-01-22 14:48:06] [STEP] sample=SRR31004501 step=exon_intersect_to_read_gene_blockN DONE
[2026-01-22 14:48:06] [STEP] sample=SRR31004501 step=exon_join_filter START
[2026-01-22 14:48:12] [STEP] sample=SRR31004501 step=exon_join_filter DONE
[2026-01-22 14:48:12] [STEP] sample=SRR31004501 step=exon_unique_read_gene START
[2026-01-22 14:48:19] [STEP] sample=SRR31004501 step=exon_unique_read_gene DONE
[2026-01-22 14:48:19] [STEP] sample=SRR31004501 step=exon_by_gene START
[2026-01-22 14:48:25] [STEP] sample=SRR31004501 step=exon_by_gene DONE
[2026-01-22 14:48:25] [STEP] sample=SRR31004501 step=cleanup_tmp DONE
[2026-01-22 14:48:25] [SAMPLE] ===== DONE SRR31004501 =====
[2026-01-22 14:48:25] [SAMPLE] ===== START SRR31004502 =====
[2026-01-22 14:48:25] [SAMPLE] bam=star/SRR31004502/SRR31004502_Aligned.sortedByCoord.out.bam
[2026-01-22 14:48:25] [SAMPLE] outdir=/datapool/home/2024561579/zs/internal_readlevel/per_sample/SRR31004502
[2026-01-22 14:48:25] [SAMPLE] sample_log=/datapool/home/2024561579/zs/internal_readlevel/logs_20260122_143935/SRR31004502.log
[2026-01-22 14:48:25] [STEP] sample=SRR31004502 step=make_blocks_r1 START
bash: bedtools: command not found
[2026-01-22 14:48:32] [STEP] sample=SRR31004502 step=make_blocks_r1 DONE
[2026-01-22 14:48:32] [STEP] sample=SRR31004502 step=make_blocks_r2 START
bash: bedtools: command not found
[2026-01-22 14:48:38] [STEP] sample=SRR31004502 step=make_blocks_r2 DONE
[2026-01-22 14:48:38] [STEP] sample=SRR31004502 step=concat_blocks START
这回是跑起来了吗
跑起来了,但跑的是“假跑”:现在这个节点环境里 bedtools 和 pv 都不存在,所以真正的核心步骤都没执行成功。
你日志里已经把原因写死了:
bash: bedtools: command not foundbash: pv: command not foundblocks lines=0 —— 因为 blocks 根本没生成出来。而且你会发现它还在继续往下“DONE”,是因为我那版 run_step 用了 bash -lc "...",在你们这个环境里 bash -lc 这条命令本身返回了 0(所以没触发 ERR trap),导致它继续跑完“空流程”。这不是你数据问题,是环境/脚本捕错方式问题。
你在终端能用 bedtools/pv,网页节点上不能用,说明网页作业没加载你交互环境。
你选一个你们集群可用的方式(我都给你写进脚本里,哪个能用就用哪个):
source .../conda.sh && conda activate mulocdeep_cpy37module load bedtools samtools(以及 pv,如果有)bash -lc 包命令,直接跑;并且强制检查 bedtools/pv 是否存在这样一旦缺软件,立刻退出,不会“假跑”。
把下面整段直接用(你只需要确认 conda 路径那行是否正确;不对就把那两行删掉改成 module load)。
bash#!/bin/bash set -euo pipefail export LC_ALL=C cd /datapool/home/2024561579/zs PER="/datapool/home/2024561579/zs/internal_readlevel/per_sample" INTRON="/datapool/home/2024561579/zs/internal_readlevel/ann/intron_region.bed" EXON="/datapool/home/2024561579/zs/internal_readlevel/ann/exon_union.bed" T=58 EXCL=2308 RUN_TAG="$(date +%Y%m%d_%H%M%S)" LOGDIR="/datapool/home/2024561579/zs/internal_readlevel/logs_${RUN_TAG}" mkdir -p "$LOGDIR" MASTER_LOG="$LOGDIR/master.log" exec > >(tee -a "$MASTER_LOG") 2>&1 log(){ echo "[$(date '+%F %T')] $*"; } CUR_SAMPLE="(none)" CUR_STEP="(init)" trap 'log "[ERROR] sample=$CUR_SAMPLE step=$CUR_STEP line=$LINENO cmd=$BASH_COMMAND"; exit 1' ERR log "[INFO] pwd=$(pwd)" log "[INFO] hostname=$(hostname)" log "[INFO] threads(T)=$T EXCL=$EXCL" log "[INFO] LOGDIR=$LOGDIR" # ===== 关键:加载你的软件环境(优先 conda,其次 module)===== # 方案A:conda(如果网页端找不到 conda.sh,就删掉这两行改用 module) if [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then source "$HOME/miniconda3/etc/profile.d/conda.sh" conda activate mulocdeep_cpy37 || true fi # 方案B:module(如果你们是 module 管理软件,打开下面几行) # module purge # module load samtools # module load bedtools # module load pv # ===== 强制检查依赖:缺一个就立刻退出(避免“假跑”)===== for x in samtools bedtools awk sort join; do command -v "$x" >/dev/null 2>&1 || { log "[FATAL] missing command: $x"; exit 2; } done # pv 可选:没有 pv 我们就自动不用进度条(不影响正确性) HAVE_PV=1 command -v pv >/dev/null 2>&1 || HAVE_PV=0 log "[INFO] pv available? $HAVE_PV" [ -s "$INTRON" ] || { log "[FATAL] INTRON not found/empty: $INTRON"; exit 2; } [ -s "$EXON" ] || { log "[FATAL] EXON not found/empty: $EXON"; exit 2; } shopt -s nullglob BAMS=(star/*/*Aligned.sortedByCoord.out.bam) log "[INFO] BAM count=${#BAMS[@]}" [ "${#BAMS[@]}" -gt 0 ] || { log "[FATAL] No BAMs matched star/*/*Aligned.sortedByCoord.out.bam"; exit 2; } mkdir -p "$PER" for BAM in "${BAMS[@]}"; do CUR_SAMPLE=$(basename "$BAM" "_Aligned.sortedByCoord.out.bam") W="$PER/$CUR_SAMPLE" mkdir -p "$W" TMPDIR="${W}/tmp.sort.$$" mkdir -p "$TMPDIR" log "[SAMPLE] ===== START $CUR_SAMPLE =====" log "[SAMPLE] bam=$BAM" log "[SAMPLE] outdir=$W" CUR_STEP="make_blocks_r1" samtools view -@ "$T" -u -F "$EXCL" -f 64 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/1";print}' > "$W/reads.blocks.r1.bed" CUR_STEP="make_blocks_r2" samtools view -@ "$T" -u -F "$EXCL" -f 128 "$BAM" \ | bedtools bamtobed -split -i - \ | awk 'BEGIN{FS=OFS="\t"}{$4=$4"/2";print}' > "$W/reads.blocks.r2.bed" CUR_STEP="concat_blocks" cat "$W/reads.blocks.r1.bed" "$W/reads.blocks.r2.bed" > "$W/reads.blocks.bed" CUR_STEP="make_read_blockN" TOTAL_LINES=$(wc -l < "$W/reads.blocks.bed") log "[INFO] $CUR_SAMPLE blocks lines=$TOTAL_LINES" if [ "$HAVE_PV" -eq 1 ]; then pv -l -s "$TOTAL_LINES" "$W/reads.blocks.bed" \ | cut -f4 \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" else cut -f4 "$W/reads.blocks.bed" \ | sort --parallel="$T" -T "$TMPDIR" \ | uniq -c \ | awk 'BEGIN{OFS="\t"}{print $2,$1}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/read.blockN.tsv" fi CUR_STEP="intron_intersect" bedtools intersect -a "$W/reads.blocks.bed" -b "$INTRON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.intronContainedBlockN.tsv" CUR_STEP="intron_join_filter" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.intronContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/intron.internal.cand.read_gene.tsv" CUR_STEP="intron_unique" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/intron.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/intron.internal.unique.read_gene.tsv" CUR_STEP="intron_by_gene" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/intron.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/intron_internal_by_gene.tsv" CUR_STEP="exon_intersect" bedtools intersect -a "$W/reads.blocks.bed" -b "$EXON" -f 1.0 -wa -wb \ | awk 'BEGIN{FS=OFS="\t"}{print $4,$10,$1,$2,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -u \ | awk 'BEGIN{FS=OFS="\t"}{k=$1"\t"$2; c[k]++} END{for(k in c) print k,c[k]}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/read_gene.exonContainedBlockN.tsv" CUR_STEP="exon_join_filter" join -t $'\t' -1 1 -2 1 "$W/read.blockN.tsv" "$W/read_gene.exonContainedBlockN.tsv" \ | awk 'BEGIN{FS=OFS="\t"}{if($4==$2) print $1,$3}' \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 -k2,2 \ > "$W/exon.internal.cand.read_gene.tsv" CUR_STEP="exon_unique" awk 'BEGIN{FS=OFS="\t"}{cnt[$1]++; g[$1]=$2} END{for(r in cnt) if(cnt[r]==1) print r,g[r]}' \ "$W/exon.internal.cand.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k2,2 -k1,1 \ > "$W/exon.internal.unique.read_gene.tsv" CUR_STEP="exon_by_gene" awk 'BEGIN{FS=OFS="\t"}{c[$2]++} END{for(g in c) print g,c[g]}' \ "$W/exon.internal.unique.read_gene.tsv" \ | sort --parallel="$T" -T "$TMPDIR" -k1,1 \ > "$W/exon_internal_by_gene.tsv" CUR_STEP="cleanup" rm -rf "$TMPDIR" log "[SAMPLE] ===== DONE $CUR_SAMPLE =====" done log "[INFO] job end time $(date)" log "[INFO] master log: $MASTER_LOG"
missing command: bedtools,那就说明网页节点必须用 module load,你把你们平台上 bedtools 的 module 名称(或 module avail bedtools 的结果)贴我,我帮你把 module 区块写成你们能用的一行。你这份日志已经很清楚了:问题不是脚本流程,而是网页作业环境没把工具带进来。