Skip to content

Linux Command Line for Bioinformatics

By Bio Help Learning35 min readUpdated FREE

Chapter 4 of 5: Pipes, redirection, loops and your first script

Paid videoThis video is part of the paid study material.Buy the study material or join membership to watchAlready bought or a member? Sign in
Chapter 4 video: pipes, redirection, loops and your first script · 16 min

Pipes: join small tools into a pipeline

You have already used the pipe, |. It sends the output of one command straight into the next one, without a temporary file. This is the core idea of the command line: many small tools, each doing one job well, joined into a pipeline.

Three dark boxes joined by pipe arrows: zcat unzips, awk keeps sequence lines, wc -l counts them, giving 6; below, four cards explain >, >>, 2> and |.
A pipe passes output from one program to the next. Redirection sends output to a file instead of the screen.
bashCopy disabled · Buy the study material or join membership to copy code
zcat data/reads_R1.fastq.gz | awk 'NR % 4 == 2' | wc -l
OutputSign in free to see the expected output. No payment needed.Sign in / Create free account
▸ Go deeper: how data flows through a pipe (pseudo-code)

Every program has three standard streams: standard input (stdin, number 0), standard output (stdout, 1) and standard error (stderr, 2). A pipe connects stdout of the left program to stdin of the right one. All programs in a pipeline run at the same time.

textCopy disabled · Buy the study material or join membership to copy code
start zcat, awk and wc together
while zcat has data:
    zcat decompresses a chunk  -> writes it to the pipe
    awk reads lines from pipe  -> writes sequence lines to the next pipe
    wc reads lines             -> adds to its count
when zcat finishes, each program sees end-of-input and exits
wc prints the final count

Because data moves in small chunks, a pipeline over a 50 GB file uses only a few megabytes of memory. Error messages go to stderr, which is not piped, so they still appear on your screen.

Redirection: save results to files

bashCopy disabled · Buy the study material or join membership to copy code
grep -v '^#' data/variants.vcf > results/variants_body.txt
echo "analysis started" > results/log.txt
echo "variants extracted" >> results/log.txt
ls nosuchfile 2> results/errors.txt
cat results/log.txt results/errors.txt
  • >: write output to a file; an existing file is replaced without warning
  • >>: add output to the end of a file
  • 2>: write error messages to a file
  • <: read input from a file (we used it with tr in Chapter 2)
  • > out.txt 2>&1: send both output and errors to the same file (order matters: file first, then 2>&1)
OutputSign in free to see the expected output. No payment needed.Sign in / Create free account
Quick check: you run sort data.txt > data.txt. What happens?

You lose the data. The shell empties data.txt for writing before sort starts reading it, so sort reads an empty file. Always write to a new file: sort data.txt > data.sorted.txt.

Exit codes: did the command work?

Every command finishes with an exit code: 0 means success, anything else means a problem. echo $? shows the code of the last command. You can chain commands on it: && runs the next command only if the first succeeded, and || runs it only if the first failed.

bashCopy disabled · Buy the study material or join membership to copy code
mkdir -p results/qc && echo "folder ready"
grep -q PASS data/variants.vcf && echo "has PASS variants" || echo "no PASS variants"
▸ Go deeper: grep’s exit code surprises people

grep returns 0 when it finds a match, 1 when it finds none, and 2 on a real error such as a missing file. “No match” is therefore treated as a failure. This matters inside strict scripts (below): n=$(grep -c pattern file) stops a script that uses set -e whenever the count is 0. Write n=$(grep -c pattern file || true) when zero is an acceptable answer.

Loops: the same command for many files

Real projects have many samples. A for loop repeats commands for each item in a list:

bashCopy disabled · Buy the study material or join membership to copy code
for s in S01 S02 S03 S04; do
  echo "Processing $s"
done
OutputSign in free to see the expected output. No payment needed.Sign in / Create free account

The list can come from file names. The shell expands the pattern into matching names before the loop starts:

bashCopy disabled · Buy the study material or join membership to copy code
for f in data/genome.fa data/genes.gtf data/variants.vcf; do
  echo "$f: $(wc -l < "$f") lines"
done
OutputSign in free to see the expected output. No payment needed.Sign in / Create free account

In a real project the loop usually runs over samples, for example for r1 in raw/*_R1.fastq.gz; do … done, and calls a tool such as fastp or FastQC on each one. The FastQC tutorial uses exactly this pattern.

▸ Go deeper: wildcards and quoting

Wildcards (globbing) are expanded by the shell, not by the command: * matches any text, ? one character, and [12] one character from the set. ls data/*.fa becomes ls data/genome.fa before ls runs. If nothing matches, Bash passes the pattern unchanged, which is why loops sometimes run once on a literal *.fastq.gz.

Quotes: single quotes keep everything literal ('$7 == "PASS"' reaches awk untouched); double quotes still expand variables ("$s" becomes S01). Always write "$f" with double quotes around variables: a file name with a space would otherwise be split into two arguments.

Your first script

When a set of commands works, save it in a script so you can run it again, share it and cite it. Create scripts/summary.sh with a text editor (nano scripts/summary.sh; save with Ctrl+O, Enter, exit with Ctrl+X) and type:

bashCopy disabled · Buy the study material or join membership to copy code
#!/usr/bin/env bash
# summary.sh: quick counts for the practice files
set -euo pipefail

genome="data/genome.fa"
reads="data/reads_R1.fastq.gz"
vcf="data/variants.vcf"

echo "Sequences in ${genome}: $(grep -c '^>' "$genome")"
echo "Reads in ${reads}: $(( $(zcat "$reads" | wc -l) / 4 ))"
echo "PASS variants in ${vcf}: $(awk -F'\t' '!/^#/ && $7 == "PASS"' "$vcf" | wc -l)"

Then make it executable and run it from the project folder:

bashCopy disabled · Buy the study material or join membership to copy code
chmod +x scripts/summary.sh
./scripts/summary.sh
./scripts/summary.sh > results/summary.txt
OutputSign in free to see the expected output. No payment needed.Sign in / Create free account
  • #!/usr/bin/env bash: the first line, called the shebang, says which program runs the script
  • set -euo pipefail: stop at the first error (-e), treat unset variables as errors (-u), and fail a pipeline if any part fails (pipefail)
  • genome="data/genome.fa": a variable; no spaces around =, and use it as "$genome"
  • chmod +x: give the file execute permission; ./ means “the script in this folder”

🔬 Researcher notes

set -euo pipefail turns silent failures into visible ones. Without pipefail, a pipeline’s exit code is that of its last command, so zcat missing.gz | wc -l “succeeds” with a count of 0. Practical rules for robust research code (Taschuk & Wilson, 2017, PLoS Comput Biol 13(4):e1005412, doi:10.1371/journal.pcbi.1005412) include exactly this: fail loudly, use relative paths inside the project, and keep inputs read-only.

Keep scripts under version control with Git so every result can be traced to the exact code that made it (Wilson et al., 2014, PLoS Biol 12(1):e1001745, doi:10.1371/journal.pbio.1001745). For multi-step analyses, workflow managers such as Snakemake and Nextflow build on the same shell commands.

Find files and check disk space

bashCopy disabled · Buy the study material or join membership to copy code
find . -name '*.gtf'
du -sh data
df -h .
  • find . -name '*.gtf': search this folder and everything below it by name
  • du -sh data: how much space a folder uses
  • df -h .: how much space is left on the disk you are on
Output (sizes depend on your system)Sign in free to see the expected output. No payment needed.Sign in / Create free account

Working on a server

Real datasets are analysed on a lab server, an HPC cluster or a cloud machine. You connect with ssh, copy files with rsync, and keep long jobs running with tmux or nohup. The server address and user name come from your lab or computing centre.

bashCopy disabled · Buy the study material or join membership to copy code
ssh [email protected]
rsync -avP data/ [email protected]:~/bhl-linux/data/
tmux new -s qc
nohup ./scripts/summary.sh > results/summary.log 2>&1 &
  • ssh: log in to a remote machine; you get the same kind of terminal there
  • rsync -avP: copy and resume interrupted transfers, keep file details (-a), show progress (-P); the trailing / on data/ copies the folder’s contents
  • tmux new -s qc: start a session that keeps running if your connection drops; detach with Ctrl+B then D, come back with tmux attach -t qc
  • nohup … &: run a command in the background and keep it running after you log out; its output goes to the log file

🔬 Researcher notes

On HPC clusters, the machine you log in to (the login node) is shared by everyone. Run only light commands there and send real work to compute nodes through the scheduler (SLURM, PBS or similar); our HPC tutorial covers this. Download large public datasets from SRA or ENA directly on the server, not on your laptop (Leinonen et al., 2011, Nucleic Acids Res 39:D19–D21, doi:10.1093/nar/gkq1019).