# 10 Bash Aliases That Will Save You Hours in Bioinformatics

By the end of this article you will be able to define ten bash aliases and shell functions that remove the most repeated keystrokes in a bioinformatics project. You will list files by modification time, scaffold a new analysis directory with one word, back up a file before you overwrite it, extract any archive without remembering the exact flags, count the reads in a FASTQ file, find out what is filling your disk, catch a job that is eating all your memory, and pull a command from last month out of your history in two keystrokes. You will also know where aliases belong, why some of these are functions rather than aliases, and why an alias that works on the command line can silently fail inside a job script.

Here is what you need before you start:

- A bash shell. This means Linux, macOS (bash is still available on recent versions even though zsh is the default), or Windows with WSL2. Everything here assumes bash, not fish or plain zsh without adaptation.
- GNU coreutils, gzip, and tar. These are present on essentially every Linux distribution. On macOS, a few flags differ, and the article flags those cases as they come up.
- unzip if you want the archive helper to handle zip files. On Debian or Ubuntu, install it with `sudo apt install unzip`.
- Write access to your home directory, because that is where your alias definitions will live.

No administrator rights are required for the aliases themselves. Every command runs as an ordinary user.

## Linux for Bioinformatics: Why Shortcuts Pay Off

A sequencing project produces an enormous amount of repetitive command line work. You check whether the newest FASTQ file finished transferring. You create a directory for each sample. You back up a reference genome index before you rebuild it. You count reads in file after file. You run `du` because the scratch partition filled up overnight and the scheduler killed your job. Each of these tasks takes ten seconds, but you perform them hundreds of times per project, and each one is a chance for a typo, a forgotten flag, or a truncated file to cost you an hour.

The core unix commands behind these tasks, such as `ls`, `cd`, `cp`, `mkdir`, `du`, `ps`, `wc`, `grep`, and `tar`, are documented in the GNU Coreutils and GNU tar manuals, and they behave the same everywhere GNU tools are installed. What changes from person to person is the wrapper around them. An alias is a short name that bash replaces with a longer command before execution. A shell function is a small named block of code that can accept arguments, run conditionals, and return a status. Together they form the cheapest possible layer of workflow automation: no new software, no dependencies, no installation, just text in a file that your shell reads every time you log in.

The distinction matters. The bash aliases in this list are the single-word commands with no arguments. The rest are functions, because bash does not support arguments in alias expansion, a point the GNU Bash manual states directly. If you have seen bash aliases bioinformatics examples online that pass arguments to plain aliases, they rely on interactive history expansion tricks that break the moment you use them in a script. Functions are the reliable format.

## Bash Aliases and Functions: The Format That Works

![Decision path asking whether the shortcut needs an argument, then whether it runs in a job script, leading to alias or function.](/article-images/illustrations/bash-aliases-bioinformatics-alias-or-function.webp)

Before the walkthrough, you need the mechanics. Getting these right once prevents most of the frustration people have with aliases.

### The format of an alias

```
alias name='expansion text'
```

The quotes matter. Without them, bash interprets spaces inside the expansion as the end of the alias definition and you get a confusing error. Check any alias with `type -a`:

```
$ alias lt='ls -lahtr'
$ type -a lt
lt is aliased to `ls -lahtr'
```

Bypass an alias for a single command by prefixing a backslash:

```
\rm bigfile.fastq
```

The backslash tells bash to skip alias expansion and run the real `rm`. This matters if you ever alias a destructive command, which is discussed later.

Remove an alias with `unalias name`. If it comes back in a new terminal, it is still defined in a startup file and you need to remove the definition there too.

### The format of a function

```
name() {
  command "$1"
}
```

Functions accept positional arguments as `$1`, `$2`, and so on, run loops and conditionals, and return an exit status. In practice, almost every useful bioinformatics shortcut is a function, and the aliases are the short special cases.

### Where definitions live

Bash reads startup files in a specific order. For an interactive login shell, which is what you get when you SSH into a server, bash looks for `~/.bash_profile`, `~/.bash_login`, or `~/.profile` and reads the first one it finds. For an interactive non-login shell, such as a new terminal window on a desktop, bash reads `~/.bashrc`. Most distributions ship a `~/.bash_profile` that sources `~/.bashrc`, which makes `~/.bashrc` the practical home for your definitions. Many Linux systems also ship a `~/.bashrc` that sources `~/.bash_aliases` if that file exists, which is the cleanest place to keep your own additions, because package updates never touch it.

After editing the file, load the changes immediately:

```
source ~/.bashrc
```

Or simply open a new terminal. If an alias works in a new terminal but not in the one you are typing in, you forgot to source. If it works in one terminal and not another, you defined it in a file that only one kind of shell reads.

### Commands for managing your setup

| Command | What it does | Typical use |
|--|--|--|
| `alias` with no arguments | Lists every defined alias | Confirm what you have loaded |
| `type -a name` | Shows whether a name is an alias, function, builtin, or executable | Diagnose why a name does something unexpected |
| `unalias name` | Removes an alias from the current session | Test a change without restarting |
| `source ~/.bashrc` | Re-reads your startup file | Apply edits immediately |
| `shopt -s expand_aliases` | Enables alias expansion in a non-interactive shell | Rarely needed, prefer functions in scripts |

One more piece of vocabulary for the rest of the article: `$1` inside a function means the first argument you pass, `$(date +%F)` means run the `date` command with the format for an ISO date and substitute the result, and `&&` means run the next command only if the previous one succeeded.

## The 10 Aliases, One at a Time

Each entry gives the definition, the reasoning, the exact output to expect, the errors you are most likely to hit, and how to verify it works.

### 1. `lt`: list files with the newest ones last

```
alias lt='ls -lahtr'
```

This is the single highest-value alias in day-to-day bioinformatics. The flags combine as follows:

- `-l` long format, showing permissions, owner, size, and modification time
- `-a` include hidden files such as `.bashrc` and `.snakemake`
- `-h` human-readable sizes, so 4.0K instead of 4096
- `-t` sort by modification time, newest first
- `-r` reverse the sort, so the newest files end up at the bottom of the listing, closest to your prompt

Run it in a directory where a job is writing output:

```
$ lt
total 4.2G
-rw-r--r-- 1 you lab 2.1G Nov 10 09:14 sample_A.fastq.gz
-rw-r--r-- 1 you lab 2.1G Nov 10 09:41 sample_B.fastq.gz
-rw-r--r-- 1 you lab  512 Nov 10 09:52 job.log
```

You can tell at a glance that `job.log` is still growing, because its timestamp is minutes ahead of the FASTQ files. A plain `ls -l` sorts alphabetically and hides the file you actually care about.

Verification: create two files a second apart, run `lt`, and confirm the later file is on the bottom line. On macOS, all five flags work as shown with the system bash. If your macOS `ls` ignores `-h` behavior differences, install GNU coreutils with Homebrew and the GNU versions become consistent with your Linux servers.

### 2. `today`: an ISO date stamp for file and directory names

```
today() {
  date +%F
}
```

This one is written as a function, not an alias, for a specific reason. Functions work inside command substitution `$( )` in any interactive shell, and you will mostly use it that way:

```
$ mkdir "results_$(today)"
$ ls -d results_*
results_2026-11-11
```

`%F` is the `date` format code for the year-month-day format, so the output is always `2026-11-11` and never a locale-dependent variant. Sorted alphabetically, date-stamped names are also sorted chronologically, which is exactly what you want when a directory fills with daily outputs.

This works on both GNU and BSD `date`, so it is safe on macOS. Verification: run `today` by itself, and confirm it prints today's date in year-month-day order. If you want a full timestamp for backups, the `bak` function below uses a finer format.

### 3. `mkcd`: make a directory and move into it

```
mkcd() {
  if [[ -z "$1" ]]
  then
    echo "usage: mkcd directory_name"
    return 1
  fi
  mkdir -p "$1" && cd "$1"
}
```

`mkdir -p` creates the directory and any missing parent directories, and does not complain if the directory already exists. The `&&` means you only `cd` in if the `mkdir` succeeded. The guard clause catches the most common mistake, calling `mkcd` with no argument, and prints a usage line instead of a cryptic error.

```
$ mkcd demo_project
$ pwd
/home/you/demo_project
```

A common failure with hand-rolled versions of this function is an unquoted variable. If you run `mkcd my project` without quotes in your habits, bash passes `my` and `project` as two separate arguments, `mkdir -p` happily creates both, and the function silently cds into the wrong one. The quotes around `"$1"` prevent exactly that. If you see `bash: cd: too many arguments`, you called the function with a directory name containing spaces and no quotes.

### 4. `newproj`: scaffold a reproducible analysis directory

```
newproj() {
  local d="${1:-proj_$(today)}"
  mkdir -p "$d"/{raw,results,scripts,metadata}
  printf "# %s\n\nCreated %s.\nraw/     original data, never edited\nresults/ script outputs only\nscripts/ analysis code\nmetadata/ sample sheets and READMEs\n" "$d" "$(today)" > "$d/README.md"
  cd "$d"
}
```

This function encodes a directory convention that many bioinformatics teams converge on independently: raw data is read-only by discipline, results are only ever produced by scripts, code lives apart from data, and metadata such as sample sheets lives in its own folder. The line `${1:-proj_$(today)}` means use the first argument if one was given, otherwise default to a name built from today's date.

```
$ newproj rna_2026_11
$ ls
metadata  raw  README.md  results  scripts
$ cat README.md
# rna_2026_11

Created 2026-11-11.
raw/     original data, never edited
results/ script outputs only
scripts/ analysis code
metadata/ sample sheets and READMEs
```

The convention is the point. Six months from now, every project you or a colleague created with `newproj` has the same shape, so anyone can find the raw reads without reading anyone's notes. If the README write fails because the disk is full, the `cd` still runs in this version, so check for the README the first time you use the function in a new environment. Verification: run `newproj test_scaffold`, list the contents, then remove the test directory with `rm -r test_scaffold`.

### 5. `bak`: a timestamped backup before you touch anything

```
bak() {
  if [[ ! -f "$1" ]]
  then
    echo "bak: no regular file named $1"
    return 1
  fi
  cp -v "$1" "$1.$(date +%Y%m%d_%H%M%S).bak"
}
```

`cp -v` prints what it copied, and the timestamp format `%Y%m%d_%H%M%S` produces a name such as `references.fa.20261111_094512.bak` that sorts chronologically and can never collide with itself. The guard clause checks `-f`, meaning the argument must be an existing regular file, so pointing `bak` at a directory fails cleanly instead of copying with the wrong behavior.

```
$ bak references.fa
'references.fa' -> 'references.fa.20261111_094512.bak'
```

The realistic use case is editing a reference index, a configuration file, or a half-finished script on a shared server where you cannot rely on version control being set up for that directory. Before overwriting, you type `bak references.fa`, edit the original, and if the edit breaks something you restore the backup with one `cp`. When you are confident, clean old backups with `ls *.bak` first so you know exactly what you are deleting.

If you see `cp: cannot stat 'refs.fa': No such file or directory`, you misspelled the filename, and the guard clause caught it before `cp` ran. If you see `cp: omitting directory`, you pointed it at a directory, which needs `cp -r` and a different strategy entirely.

### 6. `unall`: extract any archive without remembering the flags

```
unall() {
  local f="$1"
  if [[ ! -f "$f" ]]
  then
    echo "unall: no file named $f"
    return 1
  fi
  if [[ "$f" == *.tar.gz || "$f" == *.tgz ]]
  then
    tar -xzf "$f"
  elif [[ "$f" == *.tar.bz2 ]]
  then
    tar -xjf "$f"
  elif [[ "$f" == *.tar.xz ]]
  then
    tar -xJf "$f"
  elif [[ "$f" == *.tar ]]
  then
    tar -xf "$f"
  elif [[ "$f" == *.zip ]]
  then
    unzip "$f"
  elif [[ "$f" == *.gz ]]
  then
    gunzip -k "$f"
  else
    echo "unall: do not know how to extract $f"
    return 1
  fi
}
```

The flags decompose simply: `tar -x` extracts, and the second letter selects the compression, with `-z` for gzip, `-j` for bzip2, and `-J` for xz. Modern GNU tar auto-detects compression, so `tar -xf` alone works for most tarballs, but spelling the flag out costs nothing and works on older systems. `gunzip -k` keeps the original gzipped file, which is what you want with FASTQ files, since decompressing in place would break any script that expects the `.gz` name.

```
$ unall gencode_release.tar.gz
$ ls
gencode_release.tar.gz  gtf/  annotation.log
```

The most common errors here are environmental. `bash: unzip: command not found` means the zip tools are not installed, and the fix is `sudo apt install unzip` on Debian-based systems or `module load unzip` on clusters that use environment modules. `tar: This does not look like a tar archive` usually means the file downloaded as an HTML error page instead of the real archive, so open the first few lines with `head -c 300 file` and check. `gzip: stdin: unexpected end of file` means the archive is truncated, almost always because a transfer was interrupted, and you should re-download rather than work with the partial file.

### 7. `fqcount`: count the reads in a FASTQ file

```
fqcount() {
  local f="$1"
  if [[ ! -f "$f" ]]
  then
    echo "fqcount: no file named $f"
    return 1
  fi
  local n
  if [[ "$f" == *.gz ]]
  then
    n=$(( $(zcat "$f" | wc -l) / 4 ))
  else
    n=$(( $(wc -l < "$f") / 4 ))
  fi
  echo "$n reads in $f"
}
```

A FASTQ record occupies exactly four lines: the header beginning with `@`, the sequence, a separator line beginning with `+`, and the base quality scores. So the read count is the line count divided by four. For a compressed file, `zcat` streams the decompressed content to `wc -l` without writing anything to disk. For 10 files you could run this by hand, but you will run it 50 times per project, which is when the function pays for itself.

A worked example you can reproduce in seconds. Create a tiny FASTQ with two records:

```
printf '@r1\nACGTACGT\n+\nIIIIIIII\n@r2\nTTTTGGGG\n+\nIIIIIIII\n' > test.fastq
fqcount test.fastq
```

Expected output:

```
2 reads in test.fastq
```

That is 8 lines divided by 4 records per read, giving 2 reads. Scale that arithmetic to a real file: a FASTQ with 4,000,000 lines contains 1,000,000 reads.

Two caveats belong here. On macOS, `zcat` historically refers to a different program, and the portable replacement is `gzip -cd "$f" | wc -l`, so adjust the function if you work on a Mac. Second, `wc -l` counts newline characters, so a FASTQ whose final line lacks a trailing newline yields a line count one short, and with line counts the error propagates as a fourth of a read per file. Well-formed files always end with a newline, and a discrepancy between `fqcount` and your alignment tool's reported read count on the same file is a strong hint the file was truncated in transfer. The `gzip: stdin: unexpected end of file` message from the previous entry applies here too, and it means the count you just got is wrong.

### 8. `diskuse`: find out what is filling the disk

```
diskuse() {
  local d="${1:-.}"
  du -h --max-depth=1 "$d" 2>/dev/null | sort -rh | head -15
}
```

`du` estimates disk usage, `-max-depth=1` restricts the report to one directory level so you see the size of each immediate subdirectory rather than every file on the volume, `-h` prints human-readable sizes, and `sort -rh` sorts those human-readable sizes in reverse numeric order so the largest directories appear first. `2>/dev/null` discards permission errors from directories you cannot read, which is normal on shared systems. The `${1:-.}` default means run it with no argument and it examines the current directory.

```
$ diskuse
540G  ./scratch
180G  ./raw
 62G  ./references
 12G  ./results
2.1G  ./scripts
```

Now you know the overnight scheduler failure came from `scratch`, and you can descend one level with `diskuse scratch` and repeat until you find the directory holding 300 GB of intermediate BAM files nobody deleted.

Two compatibility notes. On macOS, BSD `du` spells the depth option as `-d 1` instead of `-max-depth=1`, so swap that flag if the alias is for a Mac. Also note that `du` reports apparent sizes differently across filesystems, so comparing numbers between a local disk and an NFS mount is approximate. Verification: create `du_test/a` with a 1 MB file and `du_test/b` with a 2 MB file, run `diskuse du_test`, and confirm `b` sorts above `a`.

### 9. `bigjobs`: see which processes are consuming memory and CPU

```
bigjobs() {
  ps -eo pid,etime,%cpu,%mem,cmd --sort=-%mem | head -12
}
```

`ps -e` selects all processes on the system, `-o` chooses the columns, `etime` shows elapsed time since the process started, and `-sort=-%mem` orders by the memory percentage in descending order. The output looks like this:

```
  PID     ELIDER %CPU %MEM CMD
418902    3-04:12:51 99.3 38.2 /usr/bin/minimap2 -t 16 ref.mmi reads.fq
417115    2-21:03:44 99.6 31.9 python3 assemble.py
   981    15-02:11:02  1.1  6.4 /usr/lib/systemd/systemd-journald
```

The realistic scenario is a login node that has become unusable, an interactive job that stopped responding, or the moment you realize you launched a genome assembler with default thread settings on a machine where that grabs every core and every gigabyte. `bigjobs` shows you the process ID in the first column, and you can end a process you own with `kill PID`, escalating to `kill -9 PID` if a normal `kill` is ignored after a minute. The `head -12` keeps the list short, because the interesting processes are always at the top of a memory-sorted list.

The `-sort` flag is GNU `procps` specific. On macOS, drop the `-sort` option and pipe through `sort -k4 -rn` instead, or simply run `top` and press the memory sort key. Verification: run it, confirm the first row of output is the column header, and confirm your own shell appears somewhere in the list.

### 10. `hg`: search your command history

```
hg() {
  history | grep -i "$1"
}
```

Every command you type in an interactive bash session is recorded in the history list and, on exit, written to `~/.bash_history`. `history` prints it with line numbers, and this function filters it case-insensitively. Six weeks into a project, you cannot remember the exact `bwa mem` invocation with the read group string that worked for sample A. Instead of opening old scripts or scrolling through hundreds of lines:

```
$ hg bwa
 1043  bwa mem -t 16 -R "@RG\tID:sampleA\tSM:sampleA" ref.fa sampleA.fq.gz > sampleA.sam
 1088  bwa mem -t 16 -R "@RG\tID:sampleB\tSM:sampleB" ref.fa sampleB.fq.gz > sampleB.sam
```

Each line is prefixed with its history number. To rerun one exactly, type `!1043`, which bash expands to that command. This function is also how you answer the question a reviewer or collaborator eventually asks, which is precisely which parameters you used in March. For larger-scale history searching, bash also offers reverse incremental search with `Ctrl-r`, which you type, and it fills in the most recent matching command as you type. `hg` is better when you want to see several candidates at once.

A related tip that pairs with this function: add `HISTSIZE=50000` and `HISTFILESIZE=100000` to your `~/.bashrc` to keep far more history than the common defaults, and `shopt -s histappend` so multiple open terminals append to the history file rather than overwriting each other.

## A Copy-Paste Template for Your .bash_aliases

The block below is the full template. Paste it into `~/.bash_aliases` if your `~/.bashrc` sources that file, or append it to `~/.bashrc` directly. Then run `source ~/.bashrc` and test each name with `type -a name` to confirm bash sees it. This is the bash aliases bioinformatics template in its final format, ready to adapt.

```
# ----- bioinformatics shell helpers -----
# list newest files last
alias lt='ls -lahtr'

# ISO date stamp, works in $( )
today() { date +%F }

# make a directory and enter it
mkcd() {
  if [[ -z "$1" ]]
  then
    echo "usage: mkcd directory_name"
    return 1
  fi
  mkdir -p "$1" && cd "$1"
}

# scaffold a standard analysis project
newproj() {
  local d="${1:-proj_$(today)}"
  mkdir -p "$d"/{raw,results,scripts,metadata}
  printf "# %s\n\nCreated %s.\nraw/     original data, never edited\nresults/ script outputs only\nscripts/ analysis code\nmetadata/ sample sheets and READMEs\n" "$d" "$(today)" > "$d/README.md"
  cd "$d"
}

# timestamped backup of a single file
bak() {
  if [[ ! -f "$1" ]]
  then
    echo "bak: no regular file named $1"
    return 1
  fi
  cp -v "$1" "$1.$(date +%Y%m%d_%H%M%S).bak"
}

# extract any common archive format
unall() {
  local f="$1"
  if [[ ! -f "$f" ]]
  then
    echo "unall: no file named $f"
    return 1
  fi
  if [[ "$f" == *.tar.gz || "$f" == *.tgz ]]
  then
    tar -xzf "$f"
  elif [[ "$f" == *.tar.bz2 ]]
  then
    tar -xjf "$f"
  elif [[ "$f" == *.tar.xz ]]
  then
    tar -xJf "$f"
  elif [[ "$f" == *.tar ]]
  then
    tar -xf "$f"
  elif [[ "$f" == *.zip ]]
  then
    unzip "$f"
  elif [[ "$f" == *.gz ]]
  then
    gunzip -k "$f"
  else
    echo "unall: do not know how to extract $f"
    return 1
  fi
}

# count reads in a FASTQ, gzipped or plain
fqcount() {
  local f="$1"
  if [[ ! -f "$f" ]]
  then
    echo "fqcount: no file named $f"
    return 1
  fi
  local n
  if [[ "$f" == *.gz ]]
  then
    n=$(( $(zcat "$f" | wc -l) / 4 ))
  else
    n=$(( $(wc -l < "$f") / 4 ))
  fi
  echo "$n reads in $f"
}

# largest subdirectories first
diskuse() {
  local d="${1:-.}"
  du -h --max-depth=1 "$d" 2>/dev/null | sort -rh | head -15
}

# heaviest processes by memory
bigjobs() {
  ps -eo pid,etime,%cpu,%mem,cmd --sort=-%mem | head -12
}

# search command history
hg() {
  history | grep -i "$1"
}

# keep more history and stop terminals overwriting it
HISTSIZE=50000
HISTFILESIZE=100000
shopt -s histappend
```

A quick test sequence that exercises most of the set in under a minute:

```
$ newproj alias_test
$ fqcount <(printf '@r\nACGT\n+\nIIII\n')
```

The second line uses bash process substitution to hand the function a stream, and if your version handles it, you get a count of 1. Process substitution creates a virtual file, so the `-f` test passes and the plain-file branch runs. If your environment handles this awkwardly, use the two-record `test.fastq` from the `fqcount` section instead. Then clean up:

```
$ cd ..
$ rm -r alias_test
```

A note on a bash aliases bioinformatics example you may encounter elsewhere: definitions written as `alias name='command \!*'` attempt to borrow csh-style argument substitution. Bash tolerates some of this in interactive sessions, but the GNU Bash manual is explicit that there is no mechanism for arguments in alias replacement text, and the behavior is fragile. Everything argument-taking in the template above is a function for that reason.

## Using These on a Cluster or Server

Most serious bioinformatics happens on a shared system with a scheduler such as SLURM, and that changes two things about how your definitions behave.

First, batch scripts run in a non-interactive shell. When you submit a script with `sbatch`, the script executes in a shell that does not read `~/.bashrc` and does not expand aliases at all. This is by design in bash, because alias expansion in scripts would make scripted behavior depend on the submitter's personal configuration. The practical rule is simple: aliases are for you at the interactive prompt, and anything a job depends on belongs in a real script under your project's `scripts/` directory, called by path. If you truly need alias expansion inside a bash script, the sequence is `shopt -s expand_aliases` followed by the alias definitions before their first use, but a function defined in the script or sourced from a shared file is cleaner and portable.

Second, on many clusters your home directory is shared across all compute nodes through NFS, which works in your favor: define the helpers once on the login node and they are available in every interactive session on every node. The exceptions are systems where your scratch space is local and periodically wiped, so never place anything you want to keep, including dotfiles, in scratch.

Third, a caution about startup file ordering if you use conda. The conda initialization block that `conda init` writes goes into `~/.bashrc`, and code placed above that block runs before conda is available, while code below it runs after. If a helper needs conda environments active, put it below the conda block or activate environments explicitly inside the function. Cluster environment modules change which binaries are on your `PATH`, so a function that calls a tool by bare name, such as `samtools`, only works when the right module or environment is loaded, and it will resolve to whatever version is currently first on the `PATH`.

## Common Mistakes and Limitations

![Comparison table of three helpers showing the GNU flag used on Linux and the BSD replacement needed on macOS.](/article-images/illustrations/bash-aliases-bioinformatics-gnu-bsd-flags.webp)

These are the failures seen most often when people adopt shell shortcuts in bioinformatics work.

**Aliases in scripts do nothing, silently.** A pipeline script that calls `fqcount` will fail with `fqcount: command not found` or, worse, will not mention it at all if you never tested it. Aliases are an interactive convenience. Anything that goes into a job script or a workflow file must be a real script or function.

**Aliasing destructive commands creates two different behaviors.** A common bash aliases bioinformatics example found in many dotfiles is `alias rm='rm -i'`, which prompts for confirmation. It protects you at the prompt and does nothing inside a script, so the script deletes without asking while you believe you are protected. The same divergence applies to `alias cp='cp -i'` and `alias mv='mv -i'`. If you keep such aliases, know exactly which context each behavior applies to, and prefer the backslash bypass, `\rm`, when you need the raw command at the prompt.

**Aliases are not documentation.** When you write your methods section, or when a collaborator repeats the analysis, what counts is the actual command with its actual flags, recorded in a script or a log. An alias named `run_qc` that hides three tools and nine flags helps your typing today and hides your methods tomorrow. Keep the helpers for inspection and housekeeping, and keep the analysis itself in scripts with real commands, ideally under version control.

**GNU and BSD tools differ.** The template is written for GNU coreutils, which is what you have on Linux and in WSL. On macOS, `du, max-depth` becomes `du -d`, `ps, sort` is unavailable, and `zcat` may need to become `gzip -cd`. If your aliases must run identically on a Mac laptop and a Linux cluster, either install GNU coreutils and grep on the Mac with Homebrew, or write the functions with a check on the operating system.

**Compressed files can lie to you.** `fqcount` trusts the file structure. A FASTQ truncated mid-transfer still decompresses partway, and the count you get is the count of what survived, not what was sequenced. The defense is to compare read counts against the sequencing facility's delivery report for anything that matters, and to treat `gzip: stdin: unexpected end of file` as a hard stop.

**A definition that worked yesterday can vanish.** If a cluster administrator updates the default shell configuration, or you copy a fresh `~/.bashrc` from a template, your `~/.bash_aliases` survives in the common case but is not guaranteed to. Keep your helper block in one file, keep a copy in your project repository or a dotfiles repository, and re-source after any environment change.

**Names can collide with tools.** If a bioinformatics tool or a conda environment defines a command with the same name as your helper, `type -a name` shows you every definition in resolution order, and the first line wins. This is another reason to keep helper names distinctive, as this template does.

**Shell functions run with your full permissions in your full environment.** A helper that deletes files, even with a guard clause, deletes what the argument names. Check the argument, especially when a variable or a glob expands into it, before any command that removes data. For irreversible deletes of large result directories, moving to a `trash/` folder first and emptying it manually is a safer habit than `rm -r`.

Finally, a general limitation. Shell helpers speed up work that you have already decided how to do. They do not substitute for domain judgment about which analysis is appropriate for a particular dataset, and individual projects need that judgment from someone with the relevant expertise.

## Frequently Asked Questions

### What is the difference between a bash alias and a bash function?

An alias replaces one word with a fixed string of text before bash runs the command, and it cannot accept arguments. A function is a named block of shell code that accepts arguments, runs conditionals and loops, and returns a status. Use aliases for fixed expansions such as `lt`, and functions for anything that needs a filename or an option.

### Where should I put my aliases so they load every time I log in?

Put them in `~/.bash_aliases` if your `~/.bashrc` sources that file, which is the default on most Linux distributions, or append them to `~/.bashrc` directly. Run `source ~/.bashrc` after editing, or open a new terminal.

### Why does my alias not work inside a shell script?

Bash does not expand aliases in non-interactive shells by default, and batch job scripts run non-interactively. Write the logic as a function inside the script, or as a standalone script called by path, and reserve aliases for your interactive sessions.

### How do I pass arguments to a bash alias?

You cannot do so reliably in bash, because the GNU Bash manual states there is no mechanism for arguments in the replacement text. Define a function instead, and refer to the arguments as `$1`, `$2`, and so on.

### How do I temporarily bypass an alias?

Prefix the command with a backslash, as in `\rm file`, or use `command rm file`. Both skip alias expansion and run the underlying executable, which matters when an alias changes a destructive command's behavior.

### Do these helpers work on an HPC cluster?

Yes, in interactive sessions, and on most clusters your home directory is shared so the definitions follow you to every node. They do not work inside batch scripts submitted to the scheduler, so any command a job depends on belongs in a real script under your project directory.

### How do I share my aliases with my lab or move them to a new server?

Keep the helper block in a single file such as `~/.bash_aliases`, store a copy in a version-controlled dotfiles repository, and copy or clone it onto the new machine. Then verify each name with `type -a name` before relying on it.

### Can an alias cause reproducibility or safety problems?

Yes, in two ways. Aliases make analysis commands invisible in your notes, so record real commands in scripts and logs for anything that goes into a publication, and aliases applied to destructive commands behave differently in scripts than at the prompt, so check behavior in both contexts before you trust them.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What is the difference between a bash alias and a bash function?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "An alias replaces one word with a fixed string of text before bash runs the command, and it cannot accept arguments. A function is a named block of shell code that accepts arguments, runs conditionals and loops, and returns a status. Use aliases for fixed expansions such as lt, and functions for anything that needs a filename or an option."
      }
    },
    {
      "@type": "Question",
      "name": "Where should I put my aliases so they load every time I log in?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Put them in ~/.bash_aliases if your ~/.bashrc sources that file, which is the default on most Linux distributions, or append them to ~/.bashrc directly. Run source ~/.bashrc after editing, or open a new terminal."
      }
    },
    {
      "@type": "Question",
      "name": "Why does my alias not work inside a shell script?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Bash does not expand aliases in non-interactive shells by default, and batch job scripts run non-interactively. Write the logic as a function inside the script, or as a standalone script called by path, and reserve aliases for your interactive sessions."
      }
    },
    {
      "@type": "Question",
      "name": "How do I pass arguments to a bash alias?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "You cannot do so reliably in bash, because the GNU Bash manual states there is no mechanism for arguments in the replacement text. Define a function instead, and refer to the arguments as $1, $2, and so on."
      }
    },
    {
      "@type": "Question",
      "name": "How do I temporarily bypass an alias?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Prefix the command with a backslash, as in \\rm file, or use command rm file. Both skip alias expansion and run the underlying executable, which matters when an alias changes a destructive command's behavior."
      }
    },
    {
      "@type": "Question",
      "name": "Do these helpers work on an HPC cluster?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, in interactive sessions, and on most clusters your home directory is shared so the definitions follow you to every node. They do not work inside batch scripts submitted to the scheduler, so any command a job depends on belongs in a real script under your project directory."
      }
    },
    {
      "@type": "Question",
      "name": "How do I share my aliases with my lab or move them to a new server?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Keep the helper block in a single file such as ~/.bash_aliases, store a copy in a version-controlled dotfiles repository, and copy or clone it onto the new machine. Then verify each name with type -a name before relying on it."
      }
    },
    {
      "@type": "Question",
      "name": "Can an alias cause reproducibility or safety problems?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, in two ways. Aliases make analysis commands invisible in your notes, so record real commands in scripts and logs for anything that goes into a publication, and aliases applied to destructive commands behave differently in scripts than at the prompt, so check behavior in both contexts before you trust them."
      }
    }
  ]
}
</script>