Skip to content

File Operations

Creating, copying, moving and deleting files looks trivial until ownership, timestamps, sparse files or a second filesystem are involved. The flags on this page decide whether a copy is a faithful backup or a subtly different file.

Track: Core · Interview weight: Med


Must-Know Facts

Fact Value Verify with
Create parents mkdir -p (no error if the directory exists) mkdir -pv a/b
touch Creates an empty file or updates its timestamps stat -c %y f
cp Copies content; new file gets the copier's owner and the current time ls -l
cp -a Archive: recursive, keeps mode, owner, timestamps, links, xattrs ls -l
cp -r Recursive only; does not keep ownership or times ls -l
mv on one filesystem Rename: same inode, instant ls -i
mv across filesystems Copy, then delete the source ls -i
rm Removes a name (unlinks); no recycle bin ls -l
rmdir Removes empty directories only rmdir d
Sparse file Size is larger than the blocks it uses du -h --apparent-size
install Copy plus mode and ownership in one step install -m 755 src dst
Download that fails on HTTP errors curl -fsSL -o file URL echo $?

Creating Files and Directories

mkdir a/b/c
mkdir -p a/b/c
mkdir -pv app/{conf,logs}

Output:

mkdir: cannot create directory ‘a/b/c’: No such file or directory
mkdir: created directory 'app'
mkdir: created directory 'app/conf'
mkdir: created directory 'app/logs'

-p means "parents": it creates missing levels and succeeds silently if the path exists. touch sets timestamps, which is useful for testing time-based cleanups:

touch -d '2026-01-01 09:00' conf.yml
stat -c '%n %y' conf.yml
touch conf.yml
stat -c '%n %y' conf.yml

Output:

conf.yml 2026-01-01 09:00:00.000000000 +0000
conf.yml 2026-09-16 13:55:05.349499514 +0000

Copying

A plain cp creates a new file owned by whoever runs it, with the current time. -p and -a preserve the original metadata.

touch -d '2026-01-01 09:00' conf.yml
chmod 600 conf.yml
cp conf.yml copy-plain.yml
cp -p conf.yml copy-p.yml
ls -l --time-style=+%F conf.yml copy-*.yml
sudo cp -a conf.yml /tmp/root-a.yml
sudo cp conf.yml /tmp/root-plain.yml
ls -l --time-style=+%F /tmp/root-a.yml /tmp/root-plain.yml

Output:

-rw------- 1 laborant laborant 0 2026-01-01 conf.yml
-rw------- 1 laborant laborant 0 2026-01-01 copy-p.yml
-rw------- 1 laborant laborant 0 2026-09-16 copy-plain.yml
-rw------- 1 laborant laborant 0 2026-01-01 /tmp/root-a.yml
-rw------- 1 root     root     0 2026-09-16 /tmp/root-plain.yml

sudo cp changes ownership to root

Copying application files with sudo cp produces root-owned files that the service user cannot write. Use cp -a to keep ownership, or install -o <user> -g <group> to set it explicitly.

cp app backup-app
echo "rc=$?"
cp -r app backup-app
ls backup-app
echo v1 > file.txt; echo v2 > new.txt
cp -n new.txt file.txt; cat file.txt
cp --backup=numbered new.txt file.txt; ls file.txt*

Output:

cp: -r not specified; omitting directory 'app'
rc=1
conf
logs
cp: warning: behavior of -n is non-portable and may change in future; use --update=none instead
v1
file.txt
file.txt.~1~
Flag Effect
-r / -R Copy directories recursively
-a -dR --preserve=all: the right choice for backups and migrations
-p Keep mode, ownership (as root) and timestamps
-i / -n (--update=none) Ask before overwriting / never overwrite
--backup=numbered Keep the old destination as file.~1~
--sparse=always Keep holes in sparse files

Moving and Renaming

Within one filesystem, mv changes only the directory entry, so the inode number stays the same. Across filesystems it copies the data and deletes the source.

ls -i new.txt
mv new.txt renamed.txt
ls -i renamed.txt
mv renamed.txt /dev/shm/
ls -i /dev/shm/renamed.txt

Output:

130334 new.txt
130334 renamed.txt
2 /dev/shm/renamed.txt

/dev/shm is a tmpfs, so the last move created a new file there. A cross-filesystem move of a large directory takes as long as a copy and can be interrupted halfway.


Removing

rmdir app
echo "rc=$?"
rm -r app
rm -rfv backup-app

Output:

rmdir: failed to remove 'app': Directory not empty
rc=1
removed directory 'backup-app/conf'
removed directory 'backup-app/logs'
removed directory 'backup-app'

rm -rf with an empty variable

rm -rf "$DIR/"* with DIR unset expands to rm -rf /*. Use set -u, or ${DIR:?} so the command fails when the variable is empty. GNU rm refuses rm -rf / itself (--preserve-root), not /*.

rm removes a name. The data is freed when no names and no open file descriptors remain, which is why deleting a large log that a process still writes does not free space.


Sparse Files and dd

truncate -s 1G sparse.img
ls -lh sparse.img
du -h sparse.img
du -h --apparent-size sparse.img
dd if=/dev/zero of=zero.img bs=1M count=10 status=none
ls -lh zero.img

Output:

-rw-rw-r-- 1 laborant laborant 1.0G Sep 16 13:55 sparse.img
0   sparse.img
1.0G    sparse.img
-rw-rw-r-- 1 laborant laborant 10M Sep 16 13:55 zero.img

A sparse file reports its full size to ls but uses disk blocks only where data was written. VM disk images, database files and truncate-created swap files are often sparse; du without --apparent-size shows the real usage.

dd copies raw blocks: bs is the block size, count the number of blocks, status=progress prints progress. It is used for disk images and test files; dd if=image of=/dev/sdX overwrites a disk without any confirmation.


Installing Files and Downloading

install -m 750 -d deploy/bin
install -m 755 /usr/bin/true deploy/bin/app
ls -l deploy/bin
curl -fsSL -o missing.html https://example.com/nope
echo "curl rc=$?"
ls missing.html

Output:

total 28
-rwxr-xr-x 1 laborant laborant 26936 Sep 16 13:55 app
curl: (22) The requested URL returned error: 404
curl rc=22
ls: cannot access 'missing.html': No such file or directory

With -f, curl exits non-zero on HTTP errors and writes no file; without it, the 404 page would be saved as missing.html and the script would continue. -L follows redirects, -sS hides progress but keeps errors, and wget -O file URL is the equivalent download.


Common Errors

mkdir: cannot create directory 'a/b/c': No such file or directory

Cause: a parent directory is missing.

Fix: mkdir -p a/b/c. The related rmdir: failed to remove 'app': Directory not empty means the directory still has entries; check them, then rm -r.


Interview Checkpoints

L1: What is the difference between cp -r and cp -a?

Say first: -r copies directories recursively; -a also preserves ownership, permissions, timestamps, symlinks and extended attributes.

Proof: sudo cp -a keeps the original owner and date; sudo cp produces root-owned files with the current date.

Follow-up: Which one should a backup script use, and what would you use for a remote copy?

L1: Why is mv instant for a 50 GB file on the same filesystem but slow across filesystems?

Say first: on one filesystem mv renames the directory entry; across filesystems it must copy every block and then delete the original.

Proof: ls -i shows the same inode after a local rename and a new inode after moving to /dev/shm.

Follow-up: What happens if a cross-filesystem move is interrupted?

L2: Create a 1 GiB test file instantly, and another that really uses 1 GiB.

Say first: truncate makes a sparse file; dd or fallocate allocates blocks.

Proof: truncate -s 1G sparse.img; fallocate -l 1G full.img; compare with du -h.

Follow-up: Why does ls -l not show the difference?

L2: Deploy a binary with mode 755, owned by root, in one command.

Say first: install sets mode and ownership while copying.

Proof: sudo install -o root -g root -m 755 ./app /usr/local/bin/app

Follow-up: How does that differ from cp followed by chmod? (No window with wrong permissions.)

L2: Download a release file in a script so that HTTP errors stop the script.

Say first: curl -f returns non-zero and writes nothing on an HTTP error.

Proof: curl -fsSL -o tool.tgz "$url" || exit 1

Follow-up: How would you verify the download? (sha256sum -c.)

L3: A deleted 20 GB log file did not free any disk space.

Say first: a running process still holds the file open.

Proof: sudo lsof +L1 lists the deleted file and the process; df -h changes after the process restarts or the file is truncated through /proc/<pid>/fd/<n>.

Follow-up: How should logs be rotated to avoid this? (copytruncate, or a signal that makes the app reopen its log.)

L3: After copying an application directory with sudo, the service fails with permission denied.

Say first: the copy changed ownership to root.

Proof: ls -l shows root root on files the service user must write; cp -a or chown -R app: <dir> fixes it.

Follow-up: Which copy flag would have prevented it?


Captured on Rocky Linux 10.2 and Ubuntu 24.04.4 LTS (iximiuz Labs microVMs, kernel 6.1.167), 2026-09.