Working with Files
- Working with Files
Permissions are access control, made concrete
Beyond navigating and editing, real Linux work means controlling who can do what to which file — which is exactly the discretionary access control (DAC) model introduced conceptually on the Access Control page, here in its operational form. Getting permissions right (or wrong) is the difference between a private SSH key and a leaked one, between a confined service and a privilege-escalation foothold.
This page covers file permissions (chmod, chown, special bits, umask), archiving and compression (tar, gzip/xz, zip), efficient copying/sync (rsync), and the two kinds of links the filesystem supports.
File Permissions
Linux uses a discretionary access control (DAC) model. Every file and directory has an owner (a user), an owning group, and a set of permission bits.
Reading permissions
The ls -l output shows permissions as a 10-character string:
-rwxr-xr-- 1 alice staff 4096 May 1 12:00 script.sh
^ ^ ^ ^
| | | |
| | | group owner
| | user owner
| link count
file type and permission bits
Permission string breakdown:
- r w x r - x r - -
^ ^^^ ^^^ ^^^
| | | other (everyone else)
| | group permissions
| user (owner) permissions
file type: - file, d directory, l symlink, c char device, b block device
Each group of three bits: r (read=4), w (write=2), x (execute=1).
chmod — change permissions
# Symbolic mode
chmod u+x script.sh # add execute for owner
chmod g-w file.txt # remove write for group
chmod o=r file.txt # set other to read-only
chmod a+r file.txt # add read for all (u, g, o)
chmod u+x,g-w file.txt # multiple changes
# Octal mode (most common in scripts and documentation)
chmod 755 script.sh # rwxr-xr-x
chmod 644 file.txt # rw-r--r--
chmod 600 ~/.ssh/id_rsa # rw------- (private key)
chmod 700 ~/.ssh # rwx------
chmod -R 755 /var/www # recursive
Common permission patterns:
| Octal | Symbolic | Typical use |
|---|---|---|
755 |
rwxr-xr-x |
Directories, executables |
644 |
rw-r--r-- |
Regular files |
600 |
rw------- |
Private keys, sensitive configs |
777 |
rwxrwxrwx |
World-writable (avoid) |
700 |
rwx------ |
Private directories |
664 |
rw-rw-r-- |
Group-collaborative files |
chown — change ownership
chown alice file.txt # change owner to alice
chown alice:staff file.txt # change owner and group
chown :staff file.txt # change group only (same as chgrp)
chown -R alice:staff /var/www # recursive
chown --reference=ref.txt file.txt # copy ownership from another file
chgrp — change group
chgrp staff file.txt
chgrp -R staff /var/www
Special permission bits
chmod u+s /usr/bin/program # setuid: run as file owner, not caller
chmod g+s /shared/dir # setgid: new files inherit directory's group
chmod +t /tmp # sticky bit: only owner can delete their files
# Octal: add a leading digit
chmod 4755 /usr/bin/program # setuid + 755
chmod 2775 /shared/dir # setgid + 775
chmod 1777 /tmp # sticky + 777
umask — default permission mask
umask # show current mask (e.g. 0022)
umask 027 # new files: 640 (rw-r-----), dirs: 750
umask -S # symbolic display
New file permissions = 0666 - umask; new directory permissions = 0777 - umask.
Worked example — set up a shared directory:
mkdir /shared
chown root:devteam /shared
chmod 2775 /shared # setgid: new files inherit 'devteam' group
# Now all members of devteam can read/write; files stay group-owned
⚠️
setuidand777are where permissions become security holes. Asetuid-root binary runs as root no matter who calls it, so a single buggy one is a privilege-escalation prize (find / -perm -4000enumerates them). Andchmod 777grants everyone write — never use it as a lazy fix; grant the least access that works (least privilege). The access control page works these as exploitation/defense examples.
Archiving and Compressing
tar — tape archive
tar bundles multiple files into a single archive, optionally compressed.
# Create archives
tar -czf archive.tar.gz directory/ # create gzip-compressed archive
tar -cjf archive.tar.bz2 directory/ # create bzip2-compressed archive
tar -cJf archive.tar.xz directory/ # create xz-compressed archive
tar -cf archive.tar directory/ # uncompressed archive
# Extract archives
tar -xzf archive.tar.gz # extract gzip archive
tar -xjf archive.tar.bz2 # extract bzip2 archive
tar -xJf archive.tar.xz # extract xz archive
tar -xf archive.tar.gz # auto-detect compression (GNU tar)
# Extract to specific directory
tar -xzf archive.tar.gz -C /tmp/
# List archive contents
tar -tzf archive.tar.gz
tar -tf archive.tar.gz # auto-detect
# Verbose output during operation
tar -czvf archive.tar.gz directory/
Common tar flags:
| Flag | Meaning |
|---|---|
-c |
Create archive |
-x |
Extract archive |
-t |
List contents |
-f |
Specify archive filename (must be followed by filename) |
-z |
gzip compression |
-j |
bzip2 compression |
-J |
xz compression |
-v |
Verbose (show files being processed) |
-C dir |
Change to directory before extracting |
--strip-components=N |
Strip N leading path components |
Worked example — back up and restore a directory:
# Backup with timestamp
tar -czf "backup-$(date +%Y%m%d).tar.gz" /etc/nginx/
# List before extracting
tar -tzf backup-20260501.tar.gz | head
# Extract to a staging directory
tar -xzf backup-20260501.tar.gz -C /tmp/restore/
gzip, bzip2, xz — standalone compression
gzip file.txt # compress → file.txt.gz (original removed)
gzip -d file.txt.gz # decompress (same as gunzip)
gunzip file.txt.gz # decompress
gzip -k file.txt # keep original file
gzip -l file.txt.gz # show compression ratio
bzip2 file.txt # compress → file.txt.bz2
bunzip2 file.txt.bz2 # decompress
xz file.txt # compress → file.txt.xz (best ratio, slowest)
unxz file.txt.xz # decompress
xz -k file.txt # keep original
zcat file.txt.gz # view compressed file without extracting
zless file.txt.gz # page through compressed file
zip / unzip
zip -r archive.zip directory/ # create zip (recursive)
zip archive.zip file1 file2 # add specific files
unzip archive.zip # extract
unzip -l archive.zip # list contents
unzip archive.zip -d /tmp/ # extract to directory
Copying and Renaming
See also the navigation basics page for cp and mv fundamentals. This section covers more advanced patterns.
rsync — efficient file transfer and sync
rsync copies files efficiently, transferring only changed portions. It is preferred over cp for large trees and remote copies.
rsync -av source/ destination/ # sync directories, verbose
rsync -av --delete src/ dst/ # delete files in dst not in src
rsync -avz user@host:/remote/ /local/ # sync from remote over SSH
rsync -av --exclude='*.log' src/ dst/ # exclude files by pattern
rsync -n -av src/ dst/ # dry run (show what would happen)
rsync -av --progress src/ dst/ # show per-file progress
Key rsync flags:
| Flag | Meaning |
|---|---|
-a |
Archive: recursive, preserve symlinks, permissions, timestamps, owner |
-v |
Verbose |
-z |
Compress during transfer |
-n |
Dry run |
--delete |
Remove destination files not in source |
--exclude=PATTERN |
Exclude matching files |
--progress |
Show transfer progress |
--bwlimit=KB |
Limit bandwidth |
Worked example — deploy a website directory:
# Dry run first
rsync -navz --delete build/ user@webserver:/var/www/html/
# Apply for real
rsync -avz --delete build/ user@webserver:/var/www/html/
Renaming multiple files
The rename command (available in Perl and util-linux flavors) handles bulk renames.
# Perl rename (Debian/Ubuntu: 'rename'; RHEL: 'prename')
rename 's/\.txt$/.md/' *.txt # change extension
rename 's/foo/bar/g' *.txt # replace in all filenames
rename -n 's/\.txt$/.md/' *.txt # dry run
# util-linux rename (simpler, on many systems)
rename .txt .md *.txt # replace suffix
# Using mv in a loop (portable)
for f in *.txt; do mv "$f" "${f%.txt}.md"; done
Soft Links and Hard Links
Linux supports two types of filesystem links, each with different properties.
Hard links
A hard link is an additional directory entry pointing to the same inode (the actual data on disk). All hard links to a file are equally authoritative — there is no “original.”
ln file.txt hardlink.txt # create hard link
ls -li file.txt hardlink.txt # -i shows inode number (same for both)
stat file.txt # shows link count
Properties of hard links:
- Share the same inode number and data blocks
- File data survives until all hard links are removed
- Cannot span filesystems or partitions
- Cannot link to directories (except root can on some systems)
ls -lshows the link count (second column)
Symbolic (soft) links
A symbolic link (symlink) is a special file containing a path to another file or directory. It is like a shortcut.
ln -s /path/to/target linkname # create symbolic link
ln -s ../relative/path linkname # relative symlink (preferred)
ln -sf /new/target linkname # force: replace existing link
ls -la # symlinks shown with -> target
readlink linkname # print target of symlink
readlink -f linkname # resolve all symlinks to absolute path
Properties of symbolic links:
- Contain a path string, not a direct inode reference
- Can cross filesystems and partitions
- Can link to directories
- Become “dangling” if the target is deleted or moved
- Shown in
ls -lwithlfile type and->target
Comparison
| Property | Hard link | Symbolic link |
|---|---|---|
| Points to | Inode (data) | Path string |
| Cross-filesystem | No | Yes |
| Links to directories | No (normally) | Yes |
| Survives target deletion | Yes (data intact) | No (dangling link) |
ls -l indicator |
None (same as file) | l type, -> target |
| Inode number | Same as original | Different |
Worked example — create a versioned binary symlink:
# Install python 3.12 and create a symlink
ln -s /usr/bin/python3.12 /usr/local/bin/python3
python3 --version
# Update to new version without changing scripts
ln -sf /usr/bin/python3.13 /usr/local/bin/python3
Worked example — find dangling symlinks:
find /etc -xtype l # find symlinks whose targets don't exist
Key takeaways
- Linux permissions are DAC: each file has an owner, a group, and
rwxbits for owner/group/other — read these fromls -land set them withchmod(symbolic or octal: r=4, w=2, x=1). - The special bits matter for security:
setuid(run as owner),setgid(inherit group / run as group), and the sticky bit (/tmp: only the owner deletes their files).umasksets the default for new files. tarbundles;-z/-j/-Jselect gzip/bzip2/xz compression;-c/-x/-tcreate/extract/list.zipfor cross-platform.rsync -avsyncs efficiently (only changed data), over SSH too — always-n(dry run) before--delete.- Hard links share an inode (same data, same filesystem, survive deletion of other names); symlinks are a path pointer (cross-filesystem, can target directories, dangle if the target moves).
References
- W. Shotts, The Linux Command Line — permissions, archiving, and links chapters. https://linuxcommand.org/tlcl.php
chmod(1),chown(1),umask(1posix)man pages (GNU coreutils). https://man7.org/linux/man-pages/man1/chmod.1.htmltar(1)andrsync(1)man pages. https://download.samba.org/pub/rsync/rsync.1ln(1)andsymlink(7)— links and the inode model. https://man7.org/linux/man-pages/man7/symlink.7.html
Related course pages: Access Control and Authorization · Navigation Basics · Shell and Other Basics · User Management · Host Security
🛠️ Maintenance note: permission mechanics are stable. The
renamecommand genuinely differs by distro (Perlrename/prenamevs util-linuxrename, with incompatible syntax) — verify which ships on the course VM before relying on the examples.