Linux Command Line for Bioinformatics
Chapter 2 of 5: Set up your terminal and practice folder
What you need
- A Linux terminal. On Windows 10 (version 2004 or later) or Windows 11, use WSL2 with Ubuntu. On macOS, use the built-in Terminal app. On Linux, open any terminal.
- About 1 GB of free disk space for this tutorial. Real projects need much more: plan for tens to hundreds of GB.
- No special software. Every command here is part of a standard Linux system (GNU coreutils, grep, gzip and awk).
Windows: install WSL2 and Ubuntu
Open PowerShell as Administrator (right-click the Start button, choose Terminal (Admin) or Windows PowerShell (Admin)) and run:
wsl --installRestart when asked. Ubuntu opens and asks you to choose a Linux user name and password. This password is only for Linux; you will need it for sudo commands later. From now on, open Ubuntu from the Start menu to get your terminal.
▸ Go deeper: where your Windows files are inside WSL
Your Windows C: drive appears inside Linux as /mnt/c, so your Windows Downloads folder is /mnt/c/Users/YourName/Downloads. Keep your bioinformatics projects inside the Linux home (~) instead: file access across /mnt/c is much slower, and Windows tools can break Linux line endings. To open your Linux folder in Windows Explorer, run explorer.exe . inside it.
| System | How to get a terminal | Watch out for |
|---|---|---|
| Windows 10/11 | WSL2 + Ubuntu (wsl --install) | Keep projects in ~, not /mnt/c |
| macOS | Terminal app (shell: zsh) | Some tools differ: use gzip -dc instead of zcat; sed -i '' needs the empty quotes |
| Linux (Ubuntu, Fedora…) | Any terminal app | Nothing special |
| Lab server / HPC | ssh user@server from your terminal | Do not run heavy jobs on the login node (Chapter 4) |
🔬 Researcher notes
macOS ships BSD versions of core tools, not the GNU versions found on Linux. Most everyday commands behave the same, but options differ for sed, date, stat and zcat (BSD zcat expects old .Z files). For identical behaviour across lab members, install GNU tools (for example conda install -c conda-forge coreutils grep gawk sed) or run analyses on a Linux server.
When you report methods, name the system: for example “Ubuntu 22.04 under WSL2, GNU bash 5.1”. Check versions with bash --version and ls --version.
Step 1: Create your project folder
A clear folder layout keeps raw data safe and results easy to find. We use three folders: data for inputs you never edit, results for everything you create, and scripts for your code.
cd ~
mkdir -p bhl-linux/{data,results,scripts}
cd bhl-linux
lsmkdir: make a directory (folder)-p: create parent folders as needed, and do not complain if they already exist{data,results,scripts}: brace expansion; the shell turns it into three folder names beforemkdirruns
🔬 Researcher notes
This layout follows a widely cited recommendation for computational biology projects: raw data read-only, one folder for results, one for code, and a log of what was run (Noble, 2009, PLoS Comput Biol 5(7):e1000424, doi:10.1371/journal.pcbi.1000424). To protect raw data from accidents, you can remove write permission: chmod a-w data/*.
Step 2: Create the practice files
We create five small files that look exactly like real ones: a genome (FASTA), sequencing reads (FASTQ), genes (GTF), variants (VCF) and a sample sheet. They are tiny on purpose, so you can check every answer by eye. The cat > file << 'EOF' form writes everything you type until the line EOF into the file.
The genome (FASTA):
cat > data/genome.fa << 'EOF'
>chr1 example chromosome 1
AAGCCCAATAAACCACTCTGACTGGCCGAATAGGGATATAGGCAACGACATGTGCGGCGA
CCCTTGCGACAGTGACGCTTTCGCCGTTGCCTAAACCTATTTGAAGGAGTCTAGCAGCCG
CAGTAAGGCACAATACCTCGTCCGTGTTAC
>chr2 example chromosome 2
CAGACCAAACAAGACGTCCTCTTCAATGTTTAAATGACCCTCTCGTCATAAAACCTTTCT
ACTATGTGTTCCGCAAGAATCAACAACTAC
>chrM example mitochondrion
AATGGCGCGTCGTGAATAACGCGACGGCTGAGACGAACGGCGCGT
EOFThe reads (FASTQ), six reads of 36 bases:
cat > data/reads_R1.fastq << 'EOF'
@read1/1
GAATGAAGCGCTTAAACAGCTCAGGAGCCAGTCCCC
+
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:FF
@read2/1
GAAGCGAATGGTATCGATACGTAGGAGGTGTGCCTT
+
F::FFFFFFFF::FFFFFFFFFFF:FFFFF,,:F,#
@read3/1
CCAACTATTCTTTCCAATCCTACAGATCGGAAGAGC
+
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:FF
@read4/1
CACGAAGTTCACAGAAGTGAGATTATGTCTCGTTTG
+
FFFFFFF::FFFFFFFFFFFF:FFFFFFFF,,:F,#
@read5/1
TCTTTAAGCTNGGTGTGGTGGGCACGACCCTGGACG
+
FFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:FF
@read6/1
AATGGAACAGACTTGCTTATGTGGACGTTGTATAGG
+
FFFFFFFFFFFF:FFFFFFFFFFFF:FFFFFFF:FF
EOFGTF, VCF and sample sheets are tab-separated. Tabs are invisible and easily turn into spaces when text is copied, so we type a | between columns and let the tr command (translate characters) turn every | into a real tab:
cat > data/genes.txt << 'EOF'
chr1|bhl|gene|5|120|.|+|.|gene_id "G001"; gene_name "abcA";
chr1|bhl|exon|5|40|.|+|.|gene_id "G001"; gene_name "abcA";
chr1|bhl|exon|70|120|.|+|.|gene_id "G001"; gene_name "abcA";
chr1|bhl|gene|125|148|.|-|.|gene_id "G002"; gene_name "xyzB";
chr1|bhl|exon|125|148|.|-|.|gene_id "G002"; gene_name "xyzB";
chr2|bhl|gene|10|80|.|+|.|gene_id "G003"; gene_name "lmnC";
chr2|bhl|exon|10|35|.|+|.|gene_id "G003"; gene_name "lmnC";
chr2|bhl|exon|50|80|.|+|.|gene_id "G003"; gene_name "lmnC";
EOF
cat > data/variants.txt << 'EOF'
##fileformat=VCFv4.2
##contig=<ID=chr1,length=150>
##contig=<ID=chr2,length=90>
##INFO=<ID=DP,Number=1,Type=Integer,Description="Read depth">
##FILTER=<ID=LowQual,Description="Low quality">
#CHROM|POS|ID|REF|ALT|QUAL|FILTER|INFO
chr1|12|.|A|T|58|PASS|DP=31
chr1|47|.|G|A|12|LowQual|DP=6
chr1|88|.|T|A|71|PASS|DP=40
chr1|133|.|A|C|45|PASS|DP=22
chr2|21|.|CT|C|38|PASS|DP=18
chr2|64|.|A|C|9|LowQual|DP=4
EOF
cat > data/samples.txt << 'EOF'
sample|condition
S01|control
S02|control
S03|treated
S04|treated
EOF
tr '|' '\t' < data/genes.txt > data/genes.gtf
tr '|' '\t' < data/variants.txt > data/variants.vcf
tr '|' '\t' < data/samples.txt > data/samples.tsv
rm data/*.txtFinally, compress the reads, because real FASTQ files always arrive compressed:
gzip data/reads_R1.fastq
ls -lh dataIf a file size is different, compare your file with the text above; a missing or extra line is the usual cause. Buyers of the study material get these files (and a larger practice dataset) ready to download.
▸ Go deeper: why the ‘EOF’ is in quotes
Without quotes, the shell would try to expand anything that looks like a variable (for example $1) inside the text. With 'EOF' in single quotes, the text is written exactly as typed. Use quotes whenever the content may contain $, backticks or backslashes.
Step 3: Copy, move, rename and delete
cp data/genome.fa results/genome_copy.fa
mv results/genome_copy.fa results/backup.fa
ls results
rm -i results/backup.facp source destination: copy a file (cp -rcopies a whole folder)mv source destination: move a file; moving within the same folder is how you renamerm file: delete a file;-iasks for confirmation (typey)rmdir folder: delete an empty folder
Warning: the command line has no recycle bin. rm deletes immediately and permanently, and rm -r deletes whole folders. Never run rm -r with a * until you have checked the exact list with ls first. mv and cp also silently overwrite a destination that already exists; add -i to be asked first.
Step 4: Look inside files without opening them
head -n 4 data/genome.fa
tail -n 2 data/genome.fa
wc -l data/genome.fa data/genes.gtf data/variants.vcfhead -n 4: first 4 lines (default 10)tail -n 2: last 2 lines;tail -n +2means “from line 2 to the end”, handy for skipping a headerwc -l: count lines (wcalone also counts words and characters)less -S file: scroll through a file page by page;-Sstops long lines wrapping; pressqto quit and/to searchcat file: print the whole file; fine for tiny files, a bad idea for a 10 GB FASTQ
Compressed files need one extra step. zcat unzips a .gz file on the fly and prints it, without creating an unzipped copy on disk:
zcat data/reads_R1.fastq.gz | head -n 4▸ Go deeper: why head and zcat are fast even on huge files
These tools stream: they read a small chunk, print it, and read the next chunk. Memory use stays tiny whatever the file size. When head has printed its lines it exits, and zcat stops too, so looking at the first reads of a 20 GB file takes a fraction of a second. Counting all lines still has to read the whole file, so it takes longer, but memory use stays small.
Running head directly on a .gz file prints binary garbage, because the bytes are compressed. Always put zcat (or gzip -dc on a Mac) in front.