File and Directory Commands
Server administration is full of ordinary file operations: checking a log directory, copying a configuration file, moving a release into place, or removing temporary files. The risk is rarely that the command is mysterious. The risk is that the right command is run in the wrong directory, against the wrong path, or with options that change more than intended.
Work through this article only inside ~/lab/file-commands. The concepts from How Shell and Bash Work and Command Syntax and Help Documentation are assumed: quoting, type, paths, and exit status.
Prepare a Safe Lab
Expected output:
Replace <username> mentally with your real account name. These exercises do not need sudo. If you use sudo, you may create root-owned files in your home directory and make the lab harder to clean up.
Paths and Navigation
An absolute path starts at /:
A relative path starts from the current working directory:
Both can refer to the same object, but the relative form depends on where the process is standing.
Useful navigation:
cd - returns to the previous working directory. It is not the same as cd .., which moves to the parent directory.
Common path shortcuts:
.means the current directory...means the parent directory.~is expanded by Bash to your home directory./is the directory separator and the filesystem root.
Linux filenames are case-sensitive on common server filesystems. Reports, reports, and REPORTS can be three different names.
Listing and Inspecting Files
Start with:
ls -lah means:
-l: long listing.-a: include hidden names beginning with..-h: human-readable sizes when used with long output.
ls -ld . lists the directory itself, not its contents.
Sample long output:
Read it from left to right:
- First character: object type, such as
dfor directory or-for regular file. - Permission bits: owner, group, and other access.
- Link count.
- Owner and group.
- Size.
- Modification time.
- Name.
The link count is the number of directory entries (names) that point to the same inode, not a count of copies of the file. For an ordinary file that has never been hard-linked, this is 1. For a directory, it starts at 2 even when empty: one entry is the directory's name in its parent, the other is . inside the directory itself, pointing back to the same inode. Each subdirectory adds one more, because that subdirectory's .. also points back to the parent:
count-demo shows 3: itself, its own ., and child/... A file's link count above 1 means more than one filename refers to the identical data — see Hard Links vs Symbolic Links below.
Color is convenient but not authoritative. It depends on aliases and LS_COLORS. For more reliable inspection:
file guesses content type. stat shows metadata such as inode, ownership, mode, size, and timestamps.
Hidden Files
Names beginning with . are hidden from plain ls output:
ls -A shows hidden names but omits the implied . and .. entries. Hidden files are not protected by special security rules; they are hidden by naming convention only.
Creating Files and Directories
Create a small project tree:
Expected shape:
mkdir -p creates missing parent directories and does not fail when the target directory already exists. The {config,logs,backup} part is Bash brace expansion; Bash turns it into three arguments before mkdir runs.
touch creates an empty file if it does not exist. If the file already exists, it normally updates timestamps.
Add content:
printf 'port=8080\n' > project/config/app.conf
printf 'mode=lab\n' >> project/config/app.conf
cat project/config/app.conf
Output:
Warning
> truncates an existing file before the command writes new output. That is useful in a lab and dangerous on production configuration files. Make a backup and verify the target before using redirection on important files.
Remove an empty directory:
rmdir refuses to remove non-empty directories. That limitation is a safety feature.
Copying with cp
The common forms are:
Copy a file and verify it:
cp -i project/config/app.conf project/backup/app.conf
cmp project/config/app.conf project/backup/app.conf
echo "cmp status=$?"
If cmp prints nothing and returns 0, the two files are byte-for-byte identical.
Copy a directory tree:
GNU cp -a means archive mode. It copies recursively and preserves attributes where possible. cp -r is recursive too, but it does not express the same preservation intent.
Destination rules matter:
mkdir source existing-directory
touch source/file.txt
cp -a source new-name
cp -a source existing-directory/
find . -maxdepth 3 -print
If new-name did not exist, it becomes a copy of source. If the destination is an existing directory, source is copied inside it. Check with ls -ld -- <path> before copying into a path that might already exist.
Moving and Renaming with mv
Rename a file:
mv -i project/backup/app.conf project/backup/app.conf.old
test -f project/backup/app.conf.old && echo "new name exists"
test ! -e project/backup/app.conf && echo "old name is gone"
mv can rename within one directory or move to another directory. If the destination exists, it may overwrite it; -i asks first.
On one filesystem, a rename is usually fast and atomic. Across filesystems, mv behaves more like copy-then-delete. For large data, consider available disk space, interruption, and rollback before moving.
Hard Links vs Symbolic Links
cp makes a second, independent copy of the data. A link makes a second name for the same data, and Linux gives you two different kinds with different failure behavior.
A hard link is a second directory entry pointing at the same inode:
printf 'v1\n' > project/config/app.conf
ln project/config/app.conf project/config/app.conf.hardlink
stat -c 'links=%h inode=%i name=%n' project/config/app.conf project/config/app.conf.hardlink
links=2 inode=... name=project/config/app.conf
links=2 inode=... name=project/config/app.conf.hardlink
Both names now report the same inode number and a link count of 2. Writing through either name changes the same underlying data:
Deleting one name only removes that directory entry; the data survives under the remaining name until the link count reaches 0. This is exactly why rm is described as removing a name, not "deleting a file" in the strict sense — the underlying data is only freed once its last link and every process holding it open are gone.
A symbolic link is a different kind of object: a small file whose content is a path, resolved by the kernel each time it is accessed:
ln -s app.conf project/config/app.conf.symlink
ls -l project/config/app.conf.symlink
stat -c 'links=%h inode=%i name=%n' project/config/app.conf.symlink
lrwxrwxrwx 1 admin admin 9 Jul 29 10:22 project/config/app.conf.symlink -> app.conf
links=1 inode=... name=project/config/app.conf.symlink
The leading l in ls -l and the -> target are the signal that this is a symlink, not a regular file. Its own link count and inode are unrelated to the target's.
The practical differences, and the ones most likely to come up in an interview or bite you in production:
- Deleting the target. Remove
app.confand the hard linkapp.conf.hardlinkstill works — it is not "the target," it is an equal name for the same data. The symlinkapp.conf.symlinkbecomes a dangling link:lsstill lists it, but opening it fails withNo such file or directory. - Crossing filesystems. A hard link must point to an inode on the same filesystem, because inode numbers are only unique within one filesystem;
lnrefuses across filesystems. A symlink is just a stored path string, so it can point anywhere, including across filesystems or to a path that does not exist yet. - Directories. You cannot hard-link a directory on Linux (this is disallowed to prevent loops in the directory tree). You can symlink a directory freely.
- What
rmneeds to know. Removing a symlink with plainrm symlink-nameremoves only the link, never the target. Adding a trailing slash or trying to treat a symlink to a directory as if it were the directory itself is a common source of confusion — verify withls -l -- <path>orreadlink -f -- <path>before scripting around a link.
readlink without -f shows the stored target text exactly as written. readlink -f resolves it to a full absolute path, following the link — the version you want when a script needs to know what a symlink actually points to right now.
Names with Spaces, Wildcards, and Leading Dashes
Always quote paths that may contain spaces:
Use -- when a filename may begin with -:
Preview wildcard expansion before using a destructive command:
Bash expands *.log before the command starts. If no files match, the pattern may remain literal under default Bash settings. Do not assume a wildcard matched what you intended.
Removing Files
Remove one lab file interactively:
touch project/logs/test.log
ls -l -- project/logs/test.log
rm -i -- project/logs/test.log
test ! -e project/logs/test.log && echo "file removed"
Danger
Recursive deletion can permanently remove a directory tree. Before removing a directory, verify the full path with pwd, printf '<%s>\n' "$target", and find "$target" -maxdepth 2 -print. Do not use recursive forced deletion on a production server without a tested backup or snapshot and a rollback plan.
For the lab tree, inspect first:
target="$HOME/lab/file-commands/project"
printf 'target=<%s>\n' "$target"
find "$target" -maxdepth 3 -print
Then, only if the target is correct and inside your lab:
-r removes recursively. GNU -I asks once before removing many files and is less noisy than -i, but it is still a destructive command.
Reading Common File-Operation Errors
Most file-command failures fall into a small set of error messages. Recognizing which one you got narrows the diagnosis immediately.
The path does not exist at all — check for a typo, a wrong working directory (pwd first), or a step that failed earlier without you noticing.
The path exists, or its parent directory does, but the current user's permission bits (or an ACL, if one is set) do not allow the operation. Check ownership and mode with ls -ld -- <path> and stat -- <path> before reaching for sudo — confirm you understand why access is denied, since routinely running as root to bypass this defeats the purpose of the permission system.
The command expected a file but was given a directory. This is a common copy-paste mistake when a variable that should hold a file path ends up holding a directory path instead; file -- <path> or test -f -- <path> catches it before running a command that assumes a regular file.
rmdir only removes empty directories, by design — see Remove an Empty Directory above. A non-empty target needs rm -r (or rm -rI), used only after previewing its contents with find.
Practical Scenario: Safely Update a Configuration Copy
Problem: you need to prepare a changed application config while preserving the original.
Create the original:
mkdir -p ~/lab/file-commands/scenario/{current,backup}
printf 'port=8080\nmode=production\n' > ~/lab/file-commands/scenario/current/app.conf
Back it up and verify:
cp -a ~/lab/file-commands/scenario/current/app.conf \
~/lab/file-commands/scenario/backup/app.conf.before-change
cmp ~/lab/file-commands/scenario/current/app.conf \
~/lab/file-commands/scenario/backup/app.conf.before-change
echo "backup status=$?"
Prepare a modified copy:
cp -a ~/lab/file-commands/scenario/current/app.conf \
~/lab/file-commands/scenario/current/app.conf.new
printf 'port=9090\nmode=production\n' > ~/lab/file-commands/scenario/current/app.conf.new
cat ~/lab/file-commands/scenario/current/app.conf.new
Replace only after inspection:
mv -i ~/lab/file-commands/scenario/current/app.conf.new \
~/lab/file-commands/scenario/current/app.conf
cat ~/lab/file-commands/scenario/current/app.conf
Conclusion: the workflow separates backup, edit, verification, and replacement. Real production services also need application-specific validation before reload or restart.
Common Mistakes
- Running file commands from an unknown current directory.
- Using
sudoin a home-directory lab and creating root-owned files. - Confusing
cd -withcd ... - Believing hidden files are protected. They are only hidden from default listing.
- Copying a directory without checking whether the destination already exists.
- Using unquoted variables for paths.
- Running recursive deletion before previewing the target.
- Assuming a symlink and its target are removed together, or that
rmon a symlink deletes the data it points to. - Reacting to
Permission deniedby reaching forsudobefore checking ownership and mode withls -l.
Exercises
- Build
~/lab/file-commands/ex1/{input,output,tmp}with one command, then verify it withfind. - Create a file whose name contains a space. Show one failing unquoted command and one correct quoted command.
- Copy a directory with
cp -a, verify withfind, then rename the copy withmv -i. - Create three
.logfiles and preview the wildcard expansion withechobefore deleting one of them interactively. - Create a hard link and a symbolic link to the same file. Delete the original file and use
caton each link to show which one still works and which one is now dangling. - Trigger
No such file or directory,Permission denied, andIs a directorydeliberately in your lab, and write one sentence for each explaining the cause before you fix it.
Verification criterion: after these exercises, you should be able to explain what object each command will modify before you run it.
References
- GNU Coreutils manual: https://www.gnu.org/software/coreutils/manual/coreutils.html
lsinvocation: https://www.gnu.org/software/coreutils/manual/html_node/ls-invocation.htmlcpinvocation: https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.htmlmvinvocation: https://www.gnu.org/software/coreutils/manual/html_node/mv-invocation.htmlrminvocation: https://www.gnu.org/software/coreutils/manual/html_node/rm-invocation.htmllninvocation: https://www.gnu.org/software/coreutils/manual/html_node/ln-invocation.htmlstatinvocation: https://www.gnu.org/software/coreutils/manual/html_node/stat-invocation.html- Filesystem Hierarchy Standard: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html