Lesson 9 of 14
Disk, Memory, Users, and Permissions
This lesson covers the commands that answer "is the machine full, busy, or locked down". It checks disk and memory, lists open files, shows who the current user is, and sets who may read, write, or run each file. All output below comes from a Linux container that runs as root. Places where a normal user sees something different are marked.
| Command | Purpose |
|---|---|
df |
Free and used space per filesystem |
du |
Space used by files and directories |
free |
Memory and swap usage |
uptime |
Time since boot and load averages |
uname |
Kernel and machine information |
lsof |
Files, ports, and deleted files held open by processes |
whoami, id |
Current user, user id, and groups |
chmod |
Change permission bits |
chown, chgrp |
Change owner and group |
umask |
Default permissions for new files |
sudo |
Run one command as another user |
| shebang | First line that names the interpreter of a script |
Every example sets up its own files, so a reader can repeat it in an empty directory. Output that depends on the machine (sizes, times, process ids) is marked.
Example 1: df -h shows free space per filesystem
df (disk free) reports how much space each mounted filesystem has. The -h option prints sizes in human units.
df -h /
df -h --output=source,size,used,avail,pcent,target / /tmp
df -i /Filesystem Size Used Avail Use% Mounted on
/dev/vda 252G 14G 25G 36% /
---
Filesystem Size Used Avail Use% Mounted on
/dev/vda 252G 14G 25G 36% /
/dev/vda 252G 14G 25G 36% /
---
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda 16777216 274342 16502874 2% /The --- lines in the output blocks separate the three commands. The numbers vary per machine.
| Option | Meaning |
|---|---|
-h |
Powers of 1024 (K, M, G) |
-H |
Powers of 1000 |
-T |
Add a filesystem type column |
-i |
Count inodes instead of bytes |
--output=... |
Choose the columns (GNU only) |
Passing a path makes df report the filesystem that holds it. Here / and /tmp are the same filesystem, so both rows match. Without a path, df lists every mounted filesystem, including virtual ones.
An inode is the record that stores a file's metadata. A filesystem that runs out of inodes cannot create files even when bytes are free, which is why df -i exists. A filesystem with many tiny files fills its inodes first.
Used plus Avail is smaller than Size here. The filesystem reserves a share of blocks for root, and a normal user sees Avail without that share. On macOS, df -h works the same way, but --output does not exist; use df -h and read the columns.
Example 2: du -sh totals a directory
du (disk usage) adds up the space that files take. The -s option prints one total per argument, and -h makes it readable.
mkdir -p project/src project/logs project/build
head -c 3000000 /dev/zero > project/build/app.jar
head -c 800000 /dev/zero > project/logs/app.log
head -c 20000 /dev/zero > project/src/Main.java
du -sh project
du -h project
du -sh project/*
du -h --max-depth=1 project
du -sb project/src/Main.java
du -s --apparent-size -h project/src/Main.java3.7M project
---
24K project/src
788K project/logs
2.9M project/build
3.7M project
---
2.9M project/build
788K project/logs
24K project/src
---
24K project/src
788K project/logs
2.9M project/build
3.7M project
---
20000 project/src/Main.java
20K project/src/Main.javaPlain du -h project prints every directory below, then the total last. -s prints only the total. The glob project/* gives one total per entry, which is a quick overview of one level. --max-depth=1 (or -d 1 on both GNU and macOS) gives the same view and includes the top directory.
By default du reports disk blocks, not byte counts. Main.java holds 20000 bytes, but it occupies 24K because the filesystem allocates whole 4K blocks. -b or --apparent-size reports the byte length instead. Sparse files and compressed filesystems can make the two numbers differ a lot.
du skips nothing by default and follows no symbolic links, so a link counts only as the link itself. As a normal user, directories that cannot be read produce Permission denied lines on standard error, and their contents are missing from the total.
Example 3: sort du output to find what is large
du prints in directory order. Sorting the output ranks entries by size. sort -h understands the K, M, and G suffixes.
du -h --max-depth=1 project | sort -h -r
du -ak project | sort -n -r | head -3
du -sh project/* | sort -h | tail -23.7M project
2.9M project/build
788K project/logs
24K project/src
---
3752 project
2936 project/build
2932 project/build/app.jar
---
788K project/logs
2.9M project/buildThe first command sorts human sizes from largest to smallest. The second uses -a to include files (not only directories) and -k to print kilobytes, so a plain numeric sort -n works and head -3 keeps the three largest. The third sorts ascending and keeps the last two lines, the largest entries.
sort -h is part of GNU sort and recent macOS versions. Where it is missing, use du -k with sort -n, as in the second command. A common cleanup routine runs du -h --max-depth=1 /var | sort -h -r | head and then repeats inside the largest directory. See pipes and redirection for how the | chains these commands.
Example 4: free -h shows memory
free reports total, used, and available memory plus swap. Swap is disk space the kernel uses when memory is full.
free -h
free -m | head -2
free -h -w total used free shared buff/cache available
Mem: 15Gi 692Mi 14Gi 13Mi 636Mi 15Gi
Swap: 0B 0B 0B
---
total used free shared buff/cache available
Mem: 16094 692 14994 13 636 15401
---
total used free shared buffers cache available
Mem: 15Gi 692Mi 14Gi 13Mi 39Mi 597Mi 15Gi
Swap: 0B 0B 0B| Column | Meaning |
|---|---|
total |
Installed memory visible to the system |
used |
Memory in use by processes |
free |
Memory that holds nothing at all |
shared |
Memory used by tmpfs and shared segments |
buff/cache |
Memory the kernel uses to cache file data |
available |
Estimate of memory a new process can use without swapping |
The column to read is available, not free. Linux fills idle memory with cached file data and gives it back when a process asks, so free is usually small on a busy machine even when nothing is wrong. -w splits buff/cache into two columns. -m prints mebibytes, and -g prints gibibytes. free -s 2 repeats every 2 seconds.
This container has no swap, so the Swap row is zero. A container can also report the host's memory, not its own limit. The limit lives in cgroup files, and free does not read them. macOS has no free; vm_stat and Activity Monitor show the same data.
Example 5: uptime and the load average
uptime prints the current time, the time since boot, the number of logged-in users, and the load average.
uptime
uptime -p
cat /proc/loadavg | awk '{print NF" fields"}'
nproc 15:18:31 up 11 min, 0 user, load average: 0.25, 0.18, 0.07
---
up (varies)
---
5 fields
4The time and the up time vary on every run. The three load numbers are the average count of runnable and uninterruptible processes over the last 1, 5, and 15 minutes. Processes waiting for the CPU or for disk input and output both count.
A load average is meaningful only relative to the CPU count, which nproc prints (4 here). A load of 4.0 on 4 CPUs keeps every CPU busy. A load of 4.0 on 1 CPU means processes queue. If the 1-minute value is higher than the 15-minute value, the load is rising. If it is lower, the load is falling. uptime -p prints a readable form of the time since boot. /proc/loadavg holds the same numbers plus the running process count and the last process id. Use top or htop to see which processes cause the load. macOS uptime prints a similar line, and -p does not exist there.
Example 6: uname -a identifies the system
uname prints facts about the kernel and the machine. It is the first command to run when a bug report says "it fails on Linux".
uname -a
uname -s
uname -r
uname -mLinux vm 6.18.44-fc-v64 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux
---
Linux
6.18.44-fc-v64
x86_64| Option | Prints | Value here |
|---|---|---|
-s |
Kernel name | Linux |
-n |
Host name | vm |
-r |
Kernel release | 6.18.44-fc-v64 |
-v |
Kernel build string | #1 SMP PREEMPT_DYNAMIC @0 |
-m |
Hardware architecture | x86_64 |
-a |
All of the above | the full line |
Scripts use uname -s to choose commands (Linux or Darwin) and uname -m to choose a download (x86_64, aarch64 on Linux, arm64 on Apple Silicon). uname reports the kernel, not the distribution. For the distribution name, read /etc/os-release. The -a output on macOS has a different layout but the same fields.
Example 7: lsof lists the open files of a process
lsof (list open files) shows which process holds which file. On Unix, ports, pipes, and devices are files too, so lsof covers them all. This example starts a tail -f process that keeps notes.txt open.
echo "hello" > notes.txt
tail -f notes.txt > /dev/null &
TP=$!
sleep 0.3
lsof notes.txt
lsof -p $TP | head -4
lsof -t notes.txt | wc -l
kill $TPCOMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tail PID root 3r REG 254,0 6 2040345 notes.txt
---
COMMAND FD TYPE NAME
tail cwd DIR /tmp/claude-0/cmd-l9/demo
tail rtd DIR /
tail txt REG /usr/bin/tail
---
1The process id in the first block was replaced with PID, and the second block shows four columns only. Process ids, device numbers, and inode numbers vary per run.
lsof FILE lists every process that has that file open. The columns:
| Column | Meaning |
|---|---|
FD |
File descriptor: cwd (working directory), txt (the program), a number plus mode (3r is descriptor 3, read) |
TYPE |
REG regular file, DIR directory, IPv4 socket, FIFO pipe |
NODE |
Inode number, or the protocol for sockets |
NAME |
The path or the socket address |
lsof -p PID lists everything one process has open. lsof -t prints process ids only, which suits kill $(lsof -t file) and wc -l. This command answers "which process blocks me from unmounting this disk", because lsof /mount/point lists the holders. A normal user sees only their own processes unless sudo is used. Processes are covered in processes and signals.
Example 8: lsof -i finds the process on a port
When a server fails with "address already in use", lsof -i names the process that holds the port. This example starts a throwaway HTTP server on a local port.
python3 -m http.server 8765 --bind 127.0.0.1 >/dev/null 2>&1 &
sleep 1
lsof -i :8765
lsof -iTCP -sTCP:LISTEN -n -P
lsof -ti :8765COMMAND FD TYPE NODE NAME
python3 3u IPv4 TCP localhost:8765 (LISTEN)
---
COMMAND TYPE NODE NAME
python3 IPv4 TCP 127.0.0.1:8765 (LISTEN)
---
1The columns are trimmed to fit, and the PID and DEVICE columns are omitted. The second command printed one row per listening socket; only the row for port 8765 is kept above.
| Option | Meaning |
|---|---|
-i :PORT |
Sockets that use this port |
-iTCP |
Only TCP sockets |
-sTCP:LISTEN |
Only sockets in the listening state |
-n |
Do not resolve host names (faster, shows 127.0.0.1) |
-P |
Do not convert port numbers to service names |
-t |
Print only process ids |
Without -n and -P, lsof prints localhost and may print service names such as http-alt instead of numbers. A common shutdown line is kill $(lsof -ti :8765). The process listing for ports is covered again with ss in HTTP and ports. A normal user cannot see the sockets of other users' processes without sudo.
Example 9: lsof finds deleted files that still use space
A file that is deleted while a process holds it open keeps its disk space until the process exits. df reports the space as used, but du and ls cannot find the file. lsof +L1 lists open files with a link count below 1, which are exactly these.
dd if=/dev/zero of=big.tmp bs=1M count=20 2>/dev/null
tail -f big.tmp >/dev/null &
DP=$!
sleep 0.3
rm big.tmp
lsof +L1
ls -l /proc/$DP/fd | grep big
kill $DPCOMMAND FD TYPE SIZE/OFF NLINK NODE NAME
tail 3r REG 20971520 0 2040349 /tmp/claude-0/cmd-l9/demo/big.tmp
---
/tmp/claude-0/cmd-l9/demo/big.tmp (deleted)The lsof output was filtered to the row of this process and trimmed to the columns shown.
NLINK is 0: no directory entry names the file, yet 20,971,520 bytes (20 MiB) remain allocated. lsof marks such files with (deleted) in the /proc view. Large log files that a service keeps writing after rm are the usual cause of a disk that stays full after cleanup.
Two ways to free the space: restart the process, or truncate the file through its descriptor with : > /proc/PID/fd/3. Truncating keeps the process running. The /proc path works on Linux only. macOS has no /proc; use lsof and restart the process.
Example 10: whoami prints the current user
whoami prints the name of the effective user, the user that the kernel checks permissions against.
whoami
id -un
id -u
echo $USER
sudo -n -u nobody whoamiroot
---
root
0
---
nobodyThe line for echo $USER printed an empty line in this container, because the variable is not set here. whoami and id -un read the user from the kernel, so they do not depend on environment variables. See variables and environment for how $USER is set and why a script should not trust it.
The last command runs whoami as the nobody account through sudo, which shows the name changing. A normal user sees their login name, such as alice, for the first four commands. User id 0 is always root, and many tools check for id 0 and not for the name.
Example 11: id shows user id and groups
id prints the numeric user id, group id, and every group the user belongs to. Permissions work on these numbers; names are labels.
id
id nobody
id -g
id -Gn
id -u nobodyuid=0(root) gid=0(root) groups=0(root)
---
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
---
0
root
---
65534| Option | Prints |
|---|---|
| (none) | User id, primary group, all groups |
-u |
User id only |
-g |
Primary group id only |
-G |
All group ids |
-n |
Names instead of numbers (combine with -u, -g, -G) |
id NAME inspects another account. A normal user has a line such as uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),999(docker). Group membership decides access to shared files and to the sudo command on many systems. A group change applies to new login sessions only, so a user who was added to a group must log in again before id shows it.
Example 12: reading permission bits
ls -l prints ten characters before each name. The first is the type. The next nine are three groups of rwx: owner, group, and others.
touch plain.txt; mkdir adir
printf '#!/bin/sh\necho hi\n' > run.sh
chmod 640 plain.txt; chmod 755 run.sh
ls -l plain.txt run.sh
ls -ld adir
stat -c '%A %a %U %G %n' plain.txt run.sh adir
ls -ld /tmp /usr/bin/passwd /etc/shadow-rw-r----- 1 root root 0 Oct 3 15:18 plain.txt
-rwxr-xr-x 1 root root 18 Oct 3 15:18 run.sh
drwxr-xr-x 2 root root 4096 Oct 3 15:18 adir
---
-rw-r----- 640 root root plain.txt
-rwxr-xr-x 755 root root run.sh
drwxr-xr-x 755 root root adir
---
-rw-r----- 1 root shadow 1398 Oct 3 15:14 /etc/shadow
drwxrwxrwt 503 root root 36864 Oct 3 15:18 /tmp
-rwsr-xr-x 1 root root 64152 May 30 2024 /usr/bin/passwdDates and sizes vary. Each rwx letter is one bit with a value: r is 4, w is 2, x is 1. Add the values in each group to get one digit.
| Symbolic | Number | Meaning |
|---|---|---|
rw- |
6 | Read and write |
r-x |
5 | Read and run |
r-- |
4 | Read only |
--- |
0 | No access |
So rw-r----- is 640: the owner reads and writes, the group reads, others have nothing. The bits mean different things on files and directories:
| Bit | On a file | On a directory |
|---|---|---|
r |
Read contents | List names |
w |
Change contents | Create, rename, or delete entries |
x |
Run as a program | Enter it and reach entries by name |
Three special bits appear in the last listing. s in -rwsr-xr-x is setuid: the program runs as its owner, which lets passwd edit /etc/shadow for any user. t in drwxrwxrwt is the sticky bit: in /tmp, only a file's owner may delete it, even though everyone can write to the directory. The special bits are the leading digit in four-digit modes, such as 1777 for /tmp.
stat -c is GNU. On macOS, stat -f '%Sp %Lp %Su %Sg %N' prints the same fields.
Example 13: chmod with numeric modes
chmod (change mode) sets the permission bits. A numeric mode sets all three groups at once.
touch a.txt b.txt c.sh d.txt
stat -c '%a %n' a.txt
chmod 600 a.txt; chmod 644 b.txt; chmod 755 c.sh; chmod 000 d.txt
stat -c '%a %A %n' a.txt b.txt c.sh d.txt
mkdir -p tree/sub; touch tree/f tree/sub/g
chmod -R 750 tree
stat -c '%a %n' tree tree/f tree/sub/g644 a.txt
600 -rw------- a.txt
644 -rw-r--r-- b.txt
755 -rwxr-xr-x c.sh
0 ---------- d.txt
---
750 tree
750 tree/f
750 tree/sub/gA new file starts at 644 because of the umask (Example 16). The usual modes:
| Mode | Use |
|---|---|
600 |
Private file such as an SSH key |
644 |
Normal file, readable by all |
755 |
Program or directory, runnable and enterable by all |
700 |
Private directory |
750 |
Owner full, group read and run |
-R applies the mode to a whole tree. The example shows its limit: the plain file tree/f received 750, which makes it executable. A numeric -R treats files and directories the same. Example 14 shows the X flag that avoids this.
Example 14: chmod with symbolic modes
A symbolic mode changes selected bits and leaves the rest. It has three parts: who (u owner, g group, o others, a all), an operator (+ add, - remove, = set exactly), and the permissions.
touch s.txt; chmod 644 s.txt; stat -c '%a %A' s.txt
chmod u+x s.txt; stat -c '%a %A' s.txt
chmod g+w,o-r s.txt; stat -c '%a %A' s.txt
chmod a=r s.txt; stat -c '%a %A' s.txt
chmod u=rw,go= s.txt; stat -c '%a %A' s.txt
chmod g=u s.txt; stat -c '%a %A' s.txt
mkdir pd; chmod 700 pd; stat -c '%a %A' pd
chmod +X pd s.txt; stat -c '%a %A' pd s.txt
echo data > ro.txt; chmod 444 ro.txt
(echo more >> ro.txt) 2>&1; id -u644 -rw-r--r--
744 -rwxr--r--
760 -rwxrw----
444 -r--r--r--
600 -rw-------
660 -rw-rw----
700 drwx------
711 drwx--x--x
660 -rw-rw----
---
0Reading the steps in order:
| Command | Effect |
|---|---|
u+x |
Adds execute for the owner only |
g+w,o-r |
Two changes in one call, separated by a comma |
a=r |
Sets all three groups to exactly read |
u=rw,go= |
Owner read and write; group and others set to nothing |
g=u |
Copies the owner's bits to the group |
+X |
Adds execute only to directories or to files that already have an execute bit |
+X gave the directory 711 and left the plain file s.txt at 660. This is the reason for the common line chmod -R u+rwX,go+rX dir: it opens a tree to readers without making every data file executable.
The last command shows a root difference. The file has mode 444 (nobody may write), yet the append succeeded with no error, because root bypasses read and write checks. A normal user gets Permission denied from the same command. Testing permissions as root therefore proves nothing; use sudo -u nobody as in Example 17.
Example 15: chown and chgrp change the owner
chown (change owner) sets the user and group that own a file. The form is chown USER:GROUP FILE.
touch own.txt
chown nobody own.txt; ls -l own.txt | awk '{print $3,$4,$NF}'
chown root:nogroup own.txt; ls -l own.txt | awk '{print $3,$4,$NF}'
chgrp root own.txt; ls -l own.txt | awk '{print $3,$4,$NF}'
chown :nogroup own.txt; ls -l own.txt | awk '{print $3,$4,$NF}'
mkdir -p ot/in; touch ot/in/x
chown -R nobody ot; ls -lR ot | awk 'NF>=8{print $3,$4,$NF}'
sudo -n -u nobody chown nobody own.txt 2>&1 | head -2nobody root own.txt
root nogroup own.txt
root root own.txt
root nogroup own.txt
nobody root in
nobody root x
---
chown: changing ownership of 'own.txt': Operation not permitted| Form | Effect |
|---|---|
chown USER FILE |
Change the owner only |
chown USER:GROUP FILE |
Change both |
chown :GROUP FILE |
Change the group only |
chgrp GROUP FILE |
Same as chown :GROUP FILE |
chown -R USER DIR |
Apply to a whole tree |
On Linux, only root can give a file to another user. A normal user can change the group of their own files to a group they belong to. The last command shows the refusal: nobody does not own own.txt (it belongs to root), so chown fails with Operation not permitted. In this container every chown above succeeded only because the shell runs as root. Typical use: sudo chown -R "$USER": project after a sudo command created files owned by root. The same commands work on macOS.
Example 16: umask sets default permissions
Every new file starts from a base mode, 666 for files and 777 for directories. umask names the bits to remove from that base.
umask
(umask 022; touch u1; mkdir ud1; stat -c '%a %n' u1 ud1)
(umask 077; touch u2; mkdir ud2; stat -c '%a %n' u2 ud2)
(umask 002; touch u3; mkdir ud3; stat -c '%a %n' u3 ud3)
(umask 027; umask; umask -S; touch u4; stat -c '%a %n' u4)0022
644 u1
755 ud1
600 u2
700 ud2
664 u3
775 ud3
0027
u=rwx,g=rx,o=
640 u4The parentheses start a subshell, so each umask change ends with its line and does not alter the current shell.
| Umask | Files | Directories | Use |
|---|---|---|---|
022 |
644 | 755 | Common default; others can read |
077 |
600 | 700 | Private; only the owner |
002 |
664 | 775 | Shared group work |
027 |
640 | 750 | Group reads, others excluded |
The calculation is a bit mask, not subtraction: file mode is 666 with the umask bits cleared. A new file never gets the execute bit from umask, because the base for files is 666. umask -S prints the allowed bits symbolically. A script that writes secrets can start with umask 077 so every file it creates is private from the first byte, with no window between creation and a later chmod. A shell inherits its umask, and a login file such as ~/.profile can set it.
Example 17: sudo runs one command as another user
sudo runs a command with the rights of another user, root by default, after checking the rules in /etc/sudoers. It limits elevated access to one command at a time.
sudo -n true && echo "sudo ok"
sudo whoami
sudo -u nobody id
sudo -n -u nobody sudo -n whoami 2>&1 | head -2
sudo -l -n 2>&1 | head -4
sudo -u nobody touch /root/x 2>&1 | head -1
sudo -u nobody sh -c 'cat /etc/shadow' 2>&1 | head -1
sudo sh -c 'echo x > sudo_made.txt'; ls -l sudo_made.txt | awk '{print $3,$4}'sudo ok
---
root
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
---
sudo: a password is required
---
Matching Defaults entries for root on vm:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User root may run the following commands on vm:
---
touch: cannot touch '/root/x': Permission denied
cat: /etc/shadow: Permission denied
---
root rootThe sudo -V line was left out because the version varies.
| Option | Meaning |
|---|---|
sudo CMD |
Run CMD as root |
-u USER |
Run as another user |
-n |
Never prompt; fail if a password is needed |
-l |
List what the current user may run |
-s |
Start a shell as root |
The container user is already root, so the first commands succeed without a password. A normal user is asked for their own password, and only if their account or group appears in the sudoers rules. The line sudo: a password is required shows what -n does when a password would be needed; scripts use -n so they fail and never hang.
sudo -u nobody is a safe way to test as an unprivileged user from a root shell: both writes and the read of /etc/shadow fail, as they would for any normal user. The last command shows a frequent mistake: in sudo echo x > file, the shell, not sudo, opens the file, so the redirect runs with the caller's rights. Running sh -c under sudo puts the redirect inside the elevated shell. tee is the other form: echo x | sudo tee file. See pipes and redirection.
Example 18: executable scripts and the shebang
A script runs directly when two things are true: its execute bit is set, and its first line (the shebang, #! followed by an interpreter path) names the program that runs it.
cat > hello.sh <<'XX'
#!/bin/bash
echo "Hello from $0 with $# args"
XX
ls -l hello.sh | cut -c1-10
./hello.sh
bash hello.sh a b
chmod +x hello.sh
./hello.sh a b
head -1 hello.sh-rw-r--r--
run.sh: line 84: ./hello.sh: Permission denied
Hello from hello.sh with 2 args
Hello from ./hello.sh with 2 args
#!/bin/bashThe error line starts with run.sh: line 84: because the example ran inside a batch script. An interactive shell prints bash: ./hello.sh: Permission denied.
Without the execute bit, ./hello.sh fails, but bash hello.sh works because it only reads the file. Passing $0 shows the difference: the script sees its own invocation path. Next, the portable shebang and two failure cases.
cat > env.sh <<'XX'
#!/usr/bin/env python3
import sys
print("python", sys.version_info.major, "args", len(sys.argv)-1)
XX
chmod +x env.sh; ./env.sh 1 2 3
printf 'echo no shebang\n' > ns.sh; chmod +x ns.sh; ./ns.sh
printf '#!/bin/bash\r\necho crlf\r\n' > crlf.sh; chmod +x crlf.sh; ./crlf.sh
mkdir bin; cp hello.sh bin/hi; PATH="$PWD/bin:$PATH" hi x
ls -l /bin/sh | sed 's/.* \(\/bin\/sh\)/\1/'python 3 args 3
no shebang
run.sh: line 93: ./crlf.sh: cannot execute: required file not found
Hello from /tmp/claude-0/cmd-l9/demo/bin/hi with 1 args
/bin/sh -> dash| Case | Result |
|---|---|
#!/bin/bash |
Runs that exact path |
#!/usr/bin/env python3 |
Finds python3 through PATH, which suits tools installed in different places |
| No shebang | The calling shell runs the file as shell code; other tools may fail with Exec format error |
Windows line endings (\r\n) |
The kernel looks for an interpreter named /bin/bash\r and fails |
The CRLF case prints a message that does not mention line endings. Convert the file with sed -i 's/\r$//' file (see sed and awk). A script placed in a directory on PATH runs by name, as hi shows. /bin/sh points to dash on Debian and Ubuntu, so a #!/bin/sh script must avoid Bash-only syntax. Filesystems mounted with noexec refuse to run scripts directly; bash script.sh still works there. The shebang and chmod +x behave the same on macOS.
Interview points
dfreports filesystem space anddureports space used by files. A gap between them often means a deleted file that a process still holds open;lsof +L1finds it.df -ishows inode use. A filesystem with free bytes but no free inodes cannot create files.- Read
availableinfree, notfree. Linux uses idle memory as file cache and returns it on demand. - A load average is relative to the CPU count. The 1, 5, and 15 minute values show whether the load is rising or falling. Processes waiting on disk count as load.
lsof -i :PORTnames the process that holds a port.lsof -tprints process ids for use in scripts.- Permission bits are
r=4,w=2,x=1 for owner, group, and others. On a directory,xmeans "enter" andwmeans "create or delete entries". - Numeric
chmod -Rapplies one mode to files and directories alike. SymbolicXadds execute only to directories and already executable files. umaskclears bits from666(files) and777(directories). Files never get execute bits fromumask.rootignores read and write permission checks. Test access withsudo -u nobody, not asroot.sudo echo x > filefails because the shell opens the file beforesudoruns. Usesudo teeorsudo sh -c.- A script needs the execute bit and a valid shebang. A CRLF line ending in the shebang produces a "file not found" error for an interpreter that exists.
Summary
df -hshows space per filesystem,df -ishows inodes, and a path argument selects the filesystem that holds it.du -sh DIRtotals a directory,du -h -d 1gives one level, andsort -hranks the result.ducounts disk blocks;--apparent-sizeor-bcounts bytes.free -hreports memory and swap, and theavailablecolumn estimates what a new process can use.uptimeprints time since boot and three load averages; compare them withnproc.uname -aprints kernel, host, release, and architecture;-sand-mserve scripts.lsof FILE,lsof -p PID,lsof -i :PORT, andlsof +L1list open files, process files, port holders, and deleted files that hold space.whoamiprints the effective user;idprints user id, group id, and all groups.- Permission bits are three
rwxgroups with values 4, 2, and 1; setuid (s) and sticky (t) are special bits. chmodtakes numeric modes (640) or symbolic edits (u+x,g=u,a=r,+X).chown USER:GROUPchanges ownership, and onlyrootcan give a file to another user on Linux.umasksets the bits removed from new files and directories:022gives644and755.sudo CMDruns one command as another user;-nfails instead of prompting, and-uselects the user.- A directly executable script needs
chmod +xand a shebang such as#!/bin/bashor#!/usr/bin/env python3.
