Shell by Example: Directories POSIX + Bash

Shell provides several commands for working with directories: mkdir, rmdir, ls, find, and more.

Create a directory with mkdir:

Edit
#!/bin/sh
mkdir /tmp/mydir
echo "Created /tmp/mydir"
ls /tmp
Output:
Created /tmp/mydir
mydir

Create nested directories with -p:

Edit
#!/bin/sh
mkdir -p /tmp/parent/child/grandchild
echo "Created nested directories:"
ls /tmp/parent/child/
Output:
Created nested directories:
grandchild

-p also doesn’t error if directory exists:

Edit
#!/bin/sh
mkdir -p /tmp/mydir # No error

Remove empty directory with rmdir:

Edit
#!/bin/sh
mkdir -p /tmp/mydir
rmdir /tmp/mydir
echo "Removed /tmp/mydir"
Output:
Removed /tmp/mydir

Remove nested empty directories:

Edit
#!/bin/sh
rmdir -p /tmp/parent/child/grandchild 2>/dev/null || true
echo "Removed nested directories"
Output:
Removed nested directories

List directory contents with ls:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
touch /tmp/testdir/file1.txt /tmp/testdir/file2.sh

echo "List directory:"
ls /tmp/testdir

echo "Detailed listing:"
ls -la /tmp/testdir

echo "One file per line:"
ls -1 /tmp/testdir
Output:
List directory:
file1.txt
file2.sh
Detailed listing:
total 0
drwxr-xr-x 2 root root 80 Jan  1  2025 .
drwxrwxrwt 3 root root 60 Jan  1  2025 ..
-rw-r--r-- 1 root root  0 Jan  1  2025 file1.txt
-rw-r--r-- 1 root root  0 Jan  1  2025 file2.sh
One file per line:
file1.txt
file2.sh

Hidden files (start with .):

Edit
#!/bin/sh
mkdir -p /tmp/testdir
touch /tmp/testdir/.hidden
echo "Including hidden files:"
ls -a /tmp/testdir
Output:
Including hidden files:
.
..
.hidden

Check if directory exists with -d:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
if [ -d "/tmp/testdir" ]; then
    echo "/tmp/testdir exists"
fi
Output:
/tmp/testdir exists

Change directory with cd:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
original_dir=$(pwd)
cd /tmp/testdir || exit 1
echo "Changed to: $(pwd)"
cd "$original_dir" || exit 1
echo "Back to: $(pwd)"
Output:
Changed to: /tmp/testdir
Back to: /tmp

Use subshell to avoid changing current directory:

Edit
#!/bin/sh
mkdir -p /tmp/testdir

(
    cd /tmp/testdir || exit 1
    echo "In subshell: $(pwd)"
)
echo "Still in: $(pwd)"
Output:
In subshell: /tmp/testdir
Still in: /tmp

Find files in directory with find:

Edit
#!/bin/sh
mkdir -p /tmp/findtest/sub
touch /tmp/findtest/a.txt /tmp/findtest/b.sh /tmp/findtest/sub/c.txt

echo "Find all files:"
find /tmp/findtest -type f

echo "Find only .txt files:"
find /tmp/findtest -name "*.txt"

echo "Find by type (directories):"
find /tmp/findtest -type d
Output:
Find all files:
/tmp/findtest/b.sh
/tmp/findtest/a.txt
/tmp/findtest/sub/c.txt
Find only .txt files:
/tmp/findtest/a.txt
/tmp/findtest/sub/c.txt
Find by type (directories):
/tmp/findtest
/tmp/findtest/sub

Find with depth limit with -maxdepth:

Edit
#!/bin/sh
mkdir -p /tmp/findtest
touch /tmp/findtest/file1.txt /tmp/findtest/file2.txt

echo "Max depth 1:"
find /tmp/findtest -maxdepth 1 -type f
Output:
Max depth 1:
/tmp/findtest/file2.txt
/tmp/findtest/file1.txt

Find with -exec:

Edit
#!/bin/sh
mkdir -p /tmp/findtest
touch /tmp/findtest/file1.txt /tmp/findtest/file2.txt

echo "Find files and list them:"
find /tmp/findtest -type f -exec ls {} \;
Output:
Find files and list them:
/tmp/findtest/file2.txt
/tmp/findtest/file1.txt

Find files by age with -mtime:

Edit
#!/bin/sh
mkdir -p /tmp/findtest
touch /tmp/findtest/file1.txt /tmp/findtest/file2.txt

touch -d "2 days ago" /tmp/findtest/old.txt 2>/dev/null || touch /tmp/findtest/old.txt
echo "Files modified in last day:"
find /tmp/findtest -mtime -1 -type f
Output:
Files modified in last day:
/tmp/findtest/file2.txt
/tmp/findtest/file1.txt

Directory size with du:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
mkdir -p /tmp/findtest
echo "Created test directories" >/tmp/testdir/file1.txt

echo "Directory sizes:"
du -sh /tmp/testdir
du -sh /tmp/findtest
Output:
Directory sizes:
4.0K	/tmp/testdir
0	/tmp/findtest

List subdirectory sizes with du:

Edit
#!/bin/sh
mkdir -p /tmp/findtest
touch /tmp/findtest/file1.txt /tmp/findtest/file2.txt

echo "Subdirectory sizes:"
du -h /tmp/findtest
Output:
Subdirectory sizes:
0	/tmp/findtest

Count files in directory with find and wc:

Edit
#!/bin/sh
mkdir -p /tmp/findtest
touch /tmp/findtest/file1.txt /tmp/findtest/file2.txt

echo "File count: $(find /tmp/findtest -type f | wc -l)"
Output:
File count: 2

Iterate over directory contents with for:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
touch /tmp/testdir/file1.txt /tmp/testdir/file2.txt

echo "Loop over files:"
for file in /tmp/testdir/*; do
    [ -e "$file" ] || continue
    echo "  Found: $(basename "$file")"
done
Output:
Loop over files:
  Found: file1.txt
  Found: file2.txt

Safe handling of special characters:

Edit
#!/bin/sh
mkdir -p "/tmp/spaces dir"
touch "/tmp/spaces dir/file with spaces.txt"
echo "Files with spaces:"
for file in "/tmp/spaces dir"/*; do
    echo "  $file"
done
rm -rf "/tmp/spaces dir"
Output:
Files with spaces:
  /tmp/spaces dir/file with spaces.txt

Copy directory with cp -r:

Edit
#!/bin/sh
mkdir -p /tmp/testdir
mkdir -p /tmp/testdir_copy

cp -r /tmp/testdir /tmp/testdir_copy
echo "Copied directory:"
ls /tmp/testdir_copy
Output:
Copied directory:
testdir

Move/rename directory with mv:

Edit
#!/bin/sh
mkdir -p /tmp/testdir_copy
mkdir -p /tmp/testdir_renamed

mv /tmp/testdir_copy /tmp/testdir_renamed
echo "Renamed to testdir_renamed"
Output:
Renamed to testdir_renamed

Remove directory and contents with rm -r:

Edit
#!/bin/sh
rm -rf /tmp/testdir_renamed
echo "Removed testdir_renamed"
Output:
Removed testdir_renamed

Temporary directory:

Edit
#!/bin/sh
tmpdir=$(mktemp -d)
echo "Created temp dir: $tmpdir"
rmdir "$tmpdir"
Output:
Created temp dir: /tmp/tmp.stab000000

Get home directory:

Edit
#!/bin/sh
echo "Home directory: $HOME"
echo "Tilde expansion: ~"
Output:
Home directory: /root
Tilde expansion: ~

Special directories:

Edit
#!/bin/sh
echo "Current: $PWD"
echo "Previous: ${OLDPWD:-not set}"
Output:
Current: /tmp
Previous: not set

Bash

Directory stack (pushd/popd) with error handling

Edit
#!/bin/bash
pushd /tmp >/dev/null || exit 1
echo "Pushed to: $(pwd)"
pushd /var >/dev/null || exit 1
echo "Pushed to: $(pwd)"
popd >/dev/null || exit 1
echo "Popped to: $(pwd)"
popd >/dev/null || exit 1
Output:
Pushed to: /tmp
Pushed to: /var
Popped to: /tmp

Check for empty directory:

Edit
#!/bin/sh
is_empty_dir() {
    [ -d "$1" ] && [ -z "$(ls -A "$1")" ]
}

mkdir /tmp/emptydir
if is_empty_dir /tmp/emptydir; then
    echo "/tmp/emptydir is empty"
fi
rmdir /tmp/emptydir
Output:
/tmp/emptydir is empty

Create directory only if it doesn’t exist:

Edit
#!/bin/sh
ensure_dir() {
    [ -d "$1" ] || mkdir -p "$1"
}

ensure_dir /tmp/ensured
echo "Ensured /tmp/ensured exists"
Output:
Ensured /tmp/ensured exists

Cleanup test directories

Edit
#!/bin/sh
rm -rf /tmp/testdir /tmp/findtest /tmp/ensured

echo "Directory examples complete"
Output:
Directory examples complete

« File Paths | Temporary Files »