CHAPTER 6 / 14

awk 與 sed — 文字處理的瑞士刀

awk 處理欄位、條件、運算;sed 做批次取代。掌握這兩個就能解 80% 的 GTF/BED/sample sheet 整理問題。

awk for fields, conditionals and arithmetic; sed for batch substitution. Together they crack 80% of GTF/BED/sample-sheet wrangling.

awk 的 'pattern { action }' 模型

awk 把每一行(record)依分隔符切成欄位 $1, $2, $3 ...$0 是整行)。語法:

awk 'PATTERN { ACTION }' file

對於符合 PATTERN 的每一行,執行 ACTIONBEGIN{} 在處理任何行前執行;END{} 在所有行處理完後執行。NR = 目前行號,NF = 目前行的欄位數。

awk splits each line (record) into fields $1, $2, $3 … ($0 is the whole line). The grammar:

awk 'PATTERN { ACTION }' file

For each line matching PATTERN, run ACTION. BEGIN{} runs before all input; END{} after all lines. NR = current line number, NF = number of fields on the current line.

awk生資最常用的範例

awk '{print $1, $3}' file.gtf
印出指定欄位
awk '$3=="exon"' genes.gtf
條件過濾
awk '$5 > 30' qc.tsv
數值條件
awk 'NR>1 {print}' samples.tsv
跳過 header
awk -F',' '{print $2}' samples.csv
指定分隔符
awk -F'\t' 'BEGIN{OFS="\t"} {$3=$3+1; print}' file
in/out 都 tab + 欄位運算
awk 'END {print NR " lines"}' file
行數統計
awk 'NR%4==2 {print length($0)}' f.fq
FASTQ 序列長度
awk '{a[$1]++} END{for(k in a) print k, a[k]}' f
關聯陣列頻率

awk 的 5 個生資神技

# 1. 計算 FASTQ 中的總 reads 數 + 平均長度
# Count reads + average length
zcat sample.fastq.gz | awk 'NR%4==2 {n++; s+=length($0)} END{printf "%d reads, avg %.1f bp\n", n, s/n}'
# 2. 從 GTF 提取所有 protein-coding transcripts 的 BED
# Extract protein_coding transcript intervals as BED
awk '$3=="transcript" && /protein_coding/ {print $1"\t"$4-1"\t"$5"\t"$10"\t.\t"$7}' genes.gtf \
  | tr -d '";' > protein_coding.bed
# 3. BED 0-based vs 1-based 轉換(1-based half-open → 0-based half-open 形式)
# Convert 1-based to 0-based BED start
awk -F'\t' 'BEGIN{OFS="\t"} {$2=$2-1; print}' in.bed > out.bed

# 統計 BED 各 chrom 上的 interval 數
awk '{a[$1]++} END{for(c in a) print c, a[c]}' regions.bed
# 4. VCF 中 PASS 且 QUAL > 30 的變異
# PASS variants with QUAL > 30 in a VCF
awk -F'\t' '/^#/{print; next} $7=="PASS" && $6>30' cohort.vcf > highq.vcf
# 5. 每個 sample 的 read 計數(樣本表 + 計數表 join 不夠時)
# Per-sample read count from a counts file
awk -F'\t' 'NR>1 {sums[$1]+=$3} END{for(s in sums) print s, sums[s]}' counts.tsv

sed批次取代與簡單編輯

sed 's/chr//g' input.bed
移除 chr 前綴
sed 's/^/chr/g' input.bed
加上 chr 前綴
sed -n '5,15p' file.txt
取特定行範圍
sed '/^#/d' file.gtf
刪除註解行
sed 's/\t/,/g' a.tsv > a.csv
tsv → csv
sed -i.bak 's/old/new/g' f.txt
原地修改 + 備份
⚠️

陷阱:macOS 上 sed -i 必須加空字串 sed -i '',Linux 不需要。寫跨平台 script 時可改用 sed -i.bak(兩邊都認)。

Gotcha: on macOS sed -i needs an empty string sed -i ''; Linux doesn't. For portable scripts use sed -i.bak (works on both).

awk vs sed vs grep — 該選哪一個?

工具擅長簡單口訣
grep 搜尋/篩選整行 找符合條件的「行」
cut 抽出固定欄位(tab 分隔) 切「欄」
sed 批次取代、刪行、行範圍 改寫整段文字
awk 欄位 + 條件 + 運算 + 統計 當 grep + cut 不夠用時

互動:在 mini GTF 上練習 awk + sed

💡

建議試試:awk '$3=="exon"' genes.gtfawk '$3=="gene" {print $1, $4, $5}' genes.gtfsed 's/chr//' regions.bedawk -F'\t' '{print $1}' samples.tsv | sort -u

Try: awk '$3=="exon"' genes.gtf, awk '$3=="gene" {print $1, $4, $5}' genes.gtf, sed 's/chr//' regions.bed, awk -F'\t' '{print $1}' samples.tsv | sort -u

📝 自我檢測

1. awk '$3=="exon"' genes.gtf 在做什麼?

1. What does awk '$3=="exon"' genes.gtf do?

A. 印出每行的第 3 欄A. Print field 3 of every line
B. 找包含「exon」字串的行B. Find lines containing the substring "exon"
C. 只印「第 3 欄正好是 exon」的行C. Print rows where field 3 is exactly "exon"
D. 把第 3 欄改成 exonD. Replace field 3 with "exon"

2. 想計算 FASTQ 中每個序列的長度,要選哪一個?

2. Which awk picks only the FASTQ sequence lines and prints their length?

awk 'NR%4==2 {print length($0)}'
awk 'NR%4==1 {print length($0)}'
awk '{print length($1)}'
awk 'NR%2==0 {print $0}'

3. sed 's/chr//' regions.bed 的效果?

3. Effect of sed 's/chr//' regions.bed?

A. 所有 chr 替換為大寫 CHRA. Uppercase chr → CHR
B. 移除每行的第一個 chr(chr1 → 1)B. Remove the first 'chr' on each line (chr1 → 1)
C. 移除整個檔名前綴C. Strip the file's prefix
D. 顯示包含 chr 的行D. Print rows containing chr

4. 關於 sed -i,下列何者最正確?

4. Which is most accurate about sed -i?

A. 把結果輸出到 stdoutA. Sends result to stdout
B. 一律建立新檔B. Always creates a new file
C. 修改後可以 Ctrl+Z 還原C. Editable can be undone with Ctrl+Z
D. 直接覆寫原檔;建議搭配 -i.bak 留備份D. Overwrites the original; pair with -i.bak for safety