5 Useful Linux Commands Every Sysadmin Should Use

Building a directory structure one mkdir at a time or hunting for PIDs with ps and grep wastes time on every server you manage. Here’s how mkdir -p, heredocs, tar -C, pkill, and pidwait can simplify these tasks with fewer commands.

Most of us learn a way to perform a Linux task and stick with it for years because it works, and there’s rarely a good time to look for a better approach. You might create a directory, change into it, create another directory, and repeat the process. Or you might copy a tarball into /opt just to extract it there.

These habits aren’t wrong, but they add unnecessary steps. Across multiple servers, those extra commands add up. They also make shell scripts longer and introduce more opportunities for typos.

In this guide, we’ll explore five practical techniques that replace repetitive, multi-step routines with simpler commands. We’ve tested the examples on Ubuntu 26.04 LTS and Rocky Linux 10. They should also work on other Linux distributions, unless a section notes otherwise.

Difference Between Shell Features vs. Command Options

Before diving into the examples, it’s worth understanding that these techniques fall into two categories: shell features and command options. Knowing the difference helps explain how they behave, especially in scripts.

The first category includes shell features, such as brace expansion and heredocs, Bash handles these before executing a command.

For example, when you use brace expansion with mkdir, Bash expands the braces into a list of paths before passing them to mkdir. The command itself never sees the braces. Because these features depend on the shell, their behavior can vary when you use a different shell or run a script with a different interpreter.

The second category includes command options, such as tar -C and the process-matching options available in pgrep and pkill. These are implemented by the respective utilities rather than by the shell, so their behavior doesn’t depend on which shell launches them.

1. Create a Nested Directory Tree with mkdir -p and Brace Expansion

Setting up a directory structure is a common task when deploying a new application. Suppose you’re deploying an app called myapp in the tecmint user’s home directory and need separate directories for binaries, configuration files, logs, data, and scripts.

The traditional approach is to run multiple mkdir commands, one for each directory. A quicker approach combines mkdir -p with Bash brace expansion to create the entire directory tree in a single command:

mkdir -pv ~/myapp/{bin,config/{nginx,systemd},logs,data/{uploads,cache,backups},scripts}

The -p option creates missing parent directories as needed, while -v (verbose) prints a message for each directory created. Together, they let you build the structure and verify the results in one step.

Output:

mkdir: created directory '/home/tecmint/myapp'
mkdir: created directory '/home/tecmint/myapp/bin'
mkdir: created directory '/home/tecmint/myapp/config'
mkdir: created directory '/home/tecmint/myapp/config/nginx'
mkdir: created directory '/home/tecmint/myapp/config/systemd'
mkdir: created directory '/home/tecmint/myapp/logs'
mkdir: created directory '/home/tecmint/myapp/data'
mkdir: created directory '/home/tecmint/myapp/data/uploads'
mkdir: created directory '/home/tecmint/myapp/data/cache'
mkdir: created directory '/home/tecmint/myapp/data/backups'
mkdir: created directory '/home/tecmint/myapp/scripts'

The exact wording and ordering of verbose messages may vary by implementation, but the intended directory structure remains the same.

Here’s how Bash expands the braces in this command:

  • {a,b,c} expands into separate paths for each item. For example, {logs,scripts} becomes logs and scripts.
  • Nested braces, such as config/{nginx,systemd}, generate multiple paths under the same parent directory: config/nginx and config/systemd.
  • Avoid spaces after commas. Writing {bin, logs} prevents Bash from recognizing the expression as intended, leaving the braces in the resulting argument.

Display the Directory Tree

To verify the resulting structure, use the tree command. It may not be installed on minimal Linux systems, so install it first if necessary.

On Ubuntu and Debian, run:

sudo apt install -y tree

On Rocky Linux, AlmaLinux, and RHEL, use:

sudo dnf install -y tree

Once installed, display the directory structure:

tree ~/myapp

Output:

/home/tecmint/myapp
├── bin
├── config
│   ├── nginx
│   └── systemd
├── data
│   ├── backups
│   ├── cache
│   └── uploads
├── logs
└── scripts

10 directories, 0 files

Preview Brace Expansion Before Creating Directories

Long brace expressions are easy to mistype. A misplaced comma can produce an unexpected path, so it’s useful to check the expansion before creating anything, because Bash expands braces before executing a command, you can use echo command to preview the resulting paths without modifying the filesystem:

echo ~/myapp/{bin,config/{nginx,systemd},logs}

Output:

/home/tecmint/myapp/bin /home/tecmint/myapp/config/nginx /home/tecmint/myapp/config/systemd /home/tecmint/myapp/logs

Brace Expansion Doesn’t Work in /bin/sh Scripts

One important limitation is that brace expansion is a Bash feature, not part of the POSIX shell standard. On Ubuntu and Debian, /bin/sh typically points to dash, which does not support Bash-style brace expansion.

As a result, a script beginning with #!/bin/sh may pass an expression such as {bin,config} to mkdir literally instead of expanding it into separate directory names.

If your script relies on brace expansion, use a Bash shebang:

#!/bin/bash

This ensures the script runs under Bash, where the expansion works as expected. If you need POSIX shell compatibility, use separate path arguments or a loop instead.

If this saved you from typing a dozen mkdir commands, share it with someone who still builds project folders one directory at a time.

2. Create a File with Content Using a cat Heredoc

Once the directories are in place, the next step is usually to create a configuration file. The traditional approach involves creating the file, opening it in an editor, entering the content, and saving it. That works well for occasional tasks, but it becomes tedious when you’re configuring multiple servers or automating deployments.

A heredoc (short for here document) lets you pass multiple lines of text directly to a command using a designated closing marker. Combined with cat and output redirection, it creates a file and writes its contents in one step.

For example, let’s create an environment file in the config directory from the previous example:

cat << 'EOF' > ~/myapp/config/app.env
APP_NAME=myapp
APP_PORT=8000
LOG_DIR=/home/tecmint/myapp/logs
EOF

To verify the contents, run:

cat ~/myapp/config/app.env

Output:

APP_NAME=myapp
APP_PORT=8000
LOG_DIR=/home/tecmint/myapp/logs

Here’s how the command works:

  • << 'EOF' starts the heredoc. EOF is a delimiter you choose; any marker works as long as the closing line matches it exactly.
  • The quotes around EOF prevent Bash from expanding variables and command substitutions in the heredoc body. For example, PATH=$HOME/bin is written literally rather than expanded. This is a useful default when creating configuration files.
  • > ~/myapp/config/app.env redirects the text into the file, replacing any existing contents. Use >> instead to append the text.
  • The closing EOF must appear on a line by itself, without leading or trailing spaces, unless you use a heredoc variant that explicitly permits indentation.

Expand Variables Inside a Heredoc

Sometimes you want Bash to insert dynamic values into the file, such as the current username, hostname, or deployment date. In that case, leave the delimiter unquoted so Bash expands variables and executes command substitutions before passing the text to cat.

For example:

cat << EOF > ~/myapp/DEPLOYED.txt
Deployed by: $USER
Host: $(hostname)
Date: $(date +%F)
EOF

Read the file to see the expanded values:

cat ~/myapp/DEPLOYED.txt

Example output:

Deployed by: tecmint
Host: server1
Date: 2026-10-27

The output will vary depending on the user, hostname, and date when you run the command.

Write to Root-Owned Files with sudo tee

Heredocs are also useful for creating system configuration files under directories such as /etc. However, a common mistake is to assume that prefixing a command with sudo also grants elevated permissions to its output redirection.

For example, this command fails for a regular user who lacks permission to write to the destination:

sudo cat << 'EOF' > /etc/sysctl.d/90-tecmint.conf
vm.swappiness = 10
EOF

You may see an error similar to:

bash: /etc/sysctl.d/90-tecmint.conf: Permission denied

The reason is that your shell processes the > redirection before running sudo cat. The shell attempts to open the file using your regular user’s permissions, so the operation fails before cat can run with elevated privileges.

The solution is to use sudo tee, which opens and writes the destination file with root privileges:

sudo tee /etc/sysctl.d/90-tecmint.conf > /dev/null << 'EOF'
vm.swappiness = 10
EOF

The > /dev/null redirection suppresses tee’s normal output, keeping the configuration text from being printed to the terminal. To append content instead of replacing the file, use sudo tee -a.

3. Extract a Tarball into Another Directory with tar -C

Once the application directories and configuration files are ready, the next step is often installing software distributed as a tarball. A common approach is to move the archive to its destination, change into that directory, and extract it. Alternatively, you might extract it in ~/Downloads and move the resulting directory afterward. Both approaches involve unnecessary steps.

The -C option tells tar to change to a specified directory before extracting the archive. This lets you leave the downloaded archive where it is and extract its contents directly into the intended location.

The official Go installation method provides a practical example. Go’s Linux archive can be extracted directly into /usr/local:

wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz
Note: Replace go1.25.3 with the appropriate version listed on the official website site.

The rm -rf /usr/local/go command removes an existing Go installation before extraction. The Go project recommends this approach because extracting a new version over an existing installation can leave obsolete files behind.

After extraction, the Go binaries are available in /usr/local/go/bin, even though you never changed your working directory:

ls /usr/local/go/bin

Output:

go  gofmt

Let tar Detect the Compression Type

Traditionally, you might use different tar options for different compression formats, such as -z for gzip, -j for bzip2, and -J for xz. When extracting archives, GNU tar can automatically detect common compression formats, so you generally don’t need to remember the corresponding option.

For example, the following command extracts an xz-compressed archive directly into /opt:

sudo tar -xf archive.tar.xz -C /opt

The same basic approach works with gzip, bzip2, xz, and Zstandard-compressed tar archives, provided the required decompression support is available. For .tar.zst archives, install the zstd package if it’s missing:

On Ubuntu and Debian:

sudo apt install zstd

On Rocky Linux, AlmaLinux, and RHEL:

sudo dnf install zstd

Common Mistakes with tar -C

The destination directory must already exist because tar -C changes into that directory but doesn’t create it and if the directory is missing, extraction fails with an error similar to:

tar: /opt/node: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now

You can avoid this problem by combining mkdir -p with tar -C. Another common issue is that archives often contain a versioned top-level directory, such as node-v24.11.0-linux-x64/. If you want the files inside that directory to land directly in your destination, use --strip-components=1 to remove the first path component:

sudo mkdir -p /opt/node
sudo tar -xf node-v24.11.0-linux-x64.tar.xz -C /opt/node --strip-components=1

Note: The archive filename is an example. Choose the appropriate Node.js release and architecture from the official Node.js downloads page.

Before extracting an unfamiliar archive, inspect its contents to understand its directory structure:

tar -tf archive.tar.xz | head

This displays the first few paths in the archive, helping you determine whether --strip-components=1` is appropriate. Removing too many components can flatten the directory structure incorrectly or discard useful path levels.

If you’ve been moving tarballs into /opt just to extract them, share this with a teammate who still does the same.

4. Find and Kill Processes by Name with pgrep and pkill

Eventually, you’ll need to stop a running process, whether it’s a test server, a stuck script, or an application that’s no longer responding. The traditional approach is to run ps -A | grep -i apache2, identify the relevant PIDs, and pass them to kill manually. Although this works, it takes extra steps and can produce confusing results when `grep` matches its own command line.

A simpler approach is to use pgrep to find processes and pkill to signal them by name or command-line pattern.

Let’s start a small test process so you can practise safely. Python’s built-in HTTP server is available on both Ubuntu and Rocky Linux, and the & operator runs it in the background:

python3 -m http.server 8000 &

Use pgrep to find the running process. The -a option displays each matching PID alongside its full command line, making it easier to identify the correct process:

pgrep -a python3

Example output:

4821 python3 -m http.server 8000

Your PID will differ. Once you’ve confirmed which process you want to stop, use pkill to send it a signal without copying its PID:

pkill -e -f "http.server"

Example output:

python3 killed (pid 4821)

The -f option matches the full command line, allowing pkill to find the Python server even though its process name is simply python3. The -e option reports the process name and PID for each process signaled.

Here are some useful pgrep and pkill options to remember:

  • -f matches against the full command line instead of just the process name. Use it when multiple programs share the same executable name, but make sure your pattern identifies only the intended process.
  • -x requires an exact match for the process name. For example, pkill -x ssh matches a process named ssh, not one named sshd.
  • -u tecmint restricts the search to processes owned by the tecmint user, helping avoid unintended matches on shared servers.
  • -HUP or --signal HUP sends SIGHUP instead of the default SIGTERM. SIGTERM requests a graceful shutdown, whereas SIGKILL (-9) forcibly terminates a process without giving it an opportunity to clean up.

Before using pkill with a broad pattern, preview the matches with pgrep -a using the same pattern and relevant filters. This helps you identify the processes that could be affected before sending a signal.

Stop System Services with systemctl Instead of pkill

You may see older examples that use pkill apache2 to stop a web server. For services managed by systemd, however, the recommended approach is to use systemctl stop.

Killing a service process directly can cause problems. If its systemd unit has a restart policy configured, systemd may start it again. Otherwise, the service may enter a failed state or leave systemd’s recorded service status inconsistent with your intentions.

On Ubuntu and Debian, the Apache service is named apache2:

sudo systemctl stop apache2

On Rocky Linux, AlmaLinux, and RHEL, it is named httpd:

sudo systemctl stop httpd

Reserve pkill for processes you intend to manage directly, such as your own test servers, stuck scripts, or unwanted commands running in another terminal. For systemd-managed services, use systemctl to stop the service cleanly and let the service manager handle its lifecycle.

5. Wait for a Process to Finish with pidwait

The final tip builds on pgrep and pkill but solves a different problem: waiting for a process to finish before running the next command. For example, you might want to start a package upgrade only after a long-running backup has completed.

A common workaround is a while loop that repeatedly checks for the process with pgrep and pauses with sleep. The pidwait command, provided by the procps-ng package, offers a simpler alternative by waiting for matching processes to terminate.

For example, to wait for an rsync process to finish before printing a completion message, run:

pidwait -x rsync && echo "Backup finished at $(date +%T)"

The -x option matches the process name exactly, while && ensures that the completion message is printed only if pidwait exits successfully. The message includes the current time.

One important detail is that pidwait waits for processes it successfully identifies when it starts. If no matching process exists, it returns immediately rather than waiting for a future rsync process to appear. Also, matching by process name alone can be ambiguous if multiple rsync processes are running.

pidwait availability depends on the installed procps-ng version and distribution packaging. Check whether it is available on your system with:

command -v pidwait

If it isn’t installed, you can use a polling loop as a fallback:

while pgrep -x rsync > /dev/null; do
    sleep 5
done
echo "Backup finished at $(date +%T)"

This loop checks every five seconds until no process named rsync remains. Like the pidwait example, it returns immediately if no matching process is running when the loop begins.

It also checks only for the process name, not whether a particular backup job completed successfully, so use a more specific process check or an explicit job-status mechanism when that distinction matters.

If you’ve ever written a sleep loop just to wait for a backup, share this with someone who’s still babysitting long-running jobs.

Pro Tips for Using These Commands Safely in Scripts

Now that you have all five tricks, a few habits make them safer when you move them from the terminal into scripts that run on many servers.

The biggest risk with pkill -f is a pattern that’s too broad. A pattern like pkill -f python stops every Python program running as your user, including tools you didn’t know were Python-based. Always test the pattern with pgrep -a first, and add -u to limit it to the user who owns the job.

For heredocs in scripts, quote the marker (<< 'EOF') unless you specifically need variable expansion. An unquoted heredoc that contains a $ meant for the config file, such as a password or a Nginx variable like $host, gets silently replaced with an empty string, and the result is a config file that looks fine but doesn’t work.

Finally, remember that mkdir -p only applies the -m permission option to the last directory it creates, while the parents get your default permissions.

If you need a whole path created with specific permissions and ownership, install -d -m 750 -o tecmint -g tecmint /path/to/dir is the more predictable choice.

Conclusion

You now have five commands that each replace a multi-step routine: mkdir -p with brace expansion for directory trees, heredocs with cat or sudo tee for files, tar -C for extracting archives anywhere, pgrep and pkill for finding and stopping processes by name, and pidwait for running a command once a process finishes. Each one makes your scripts shorter and removes chances for a typo along the way.

If you’d like to build more habits like these, our 100+ Essential Linux Commands course on Pro TecMint covers the everyday commands sysadmins rely on with practical examples.

When you’re ready to turn tricks like heredocs and brace expansion into full scripts, Bash Scripting for Beginners walks through scripting from the first line to error handling.

We’d love to hear which of these tips you already use and which one is new to you. Do you have your own one-liner that replaced a long routine on your servers?

Have you ever been caught by a pkill -f pattern that matched more than you expected? Share your favourite command-line shortcuts in the comments below.

Two small things before you go

If something didn’t work on your setup, leave a comment and tell me which distro and version you’re on. I read every one, and most of these articles got better because a reader pointed out what was missing.

And if it did work, share it. TecMint has stayed free for 14 years because people pass these articles around, and that traffic is what pays for writing the next one.

Leave a comment
Get two free Linux courses

Learn the Linux basics with two free courses and join 34,000+ readers getting practical TecMint tutorials and selected Pro TecMint articles every week.

Pro TecMint
Get lifetime access for $99

If you want to go beyond the free tutorials, Pro gives you 18 courses, 5 ebooks, and every future course I add. One payment, lifetime access, no renewals.

Avishek
Avishek
Contributor
About the author

A Passionate GNU/Linux Enthusiast and Software Developer with over a decade in the field of Linux and Open Source technologies.

18 Comments

Leave a Reply
    • @sampath, I would have loved do send you every important commands on your email id but practically i am too busy and have no time. You may use the search bar at the top of Tecmint.com to search posts already published. We are adding new posts on daily basis. Keep learning. keep connected. If you have any query related to Linux/FOSS, ask us at linuxsay.com

      Reply
  1. I really, really like your Linux tricks and shortcuts, I have been a Linux enthusiast for a while by now and I am making a lot of progress since I started swimming in this ocean, thank you very much but I have a question regarding your free Ebooks and that question is: Does everyone have to belong to a company or corporation in order to get any of the books you offer? I am actually studying and practicing on my very own and although I have a job, my job does not involve any computer work since I work in the food service industry, so I have no employer to mention in order to obtain one of your free ebooks. How does it work?

    Reply
    • @Jose,
      Not like that, but you should fill all the information to get the free ebooks and this is one time registration only..

      Reply
  2. Try:

    $ tar xvf firefox-37.0.2.tar.bz2 -C /opt/

    Tar can detect the file format and tar xf FILENAME is all you need to unpack a file ;)

    Reply
    • @ Tomas

      $ tar cf will extract but wont be verbose. To produce a verbose output i used (-v). Here all i needed was to tell people to use option (-C). Please go through the post, once again!

      Reply
  3. Brace expansion is very usefull, here is another usefull trick for you. This will create a backupfile with the “.bak” extension.(there should be no spaces in between..)
    cp somefile{,.bak}

    Reply
    • Thanks Krishna for your Feedback. Happy to know, it was helpful for you.
      Keep connected for more such posts.

      Reply
    • Agree @Mohamed Magdy.
      I wrote the post from the point of view of doing it quickly and efficiently and add it to habit.

      Reply
  4. Hi, most part of day I usually use ‘pgrep …’ then ‘kill -9 ..’, so, about pkill…I need to start to use it.

    Very useful this article, thanks brow!

    Reply

Got Something to Say? Join the Discussion...

Thank you for taking the time to share your thoughts with us. We appreciate your decision to leave a comment and value your contribution to the discussion. It's important to note that we moderate all comments in accordance with our comment policy to ensure a respectful and constructive conversation.

Rest assured that your email address will remain private and will not be published or shared with anyone. We prioritize the privacy and security of our users.