Bash and Linux

Read and Set Permissions

easyUsers and permissions Must-do

Problem statement

Read a permission string like -rwxr-xr-x, turn it into a number like 755, and set permissions with chmod in both number form and letter form. "Permission denied" is one of the most common errors on any server, and the fix is almost always reading these bits and changing the right one.

The script creates three files in an empty folder:

deploy.sh (a script that should run)

Bash
#!/bin/bash
echo deploying

app.conf (a config others may read)

TEXT
port=8080

id_ed25519 (a private key only you may read)

TEXT
FAKE PRIVATE KEY FOR PRACTICE

Do these steps in order:

  1. Set the script to 755, the config to 644 and the key to 600, then print each mode as a number and as a string.
  2. With letter form: give the group write access to the config, then take all access away from others, then remove execute from group and others on the script. Print the mode after each change.
  3. Turn the strings rw-r-----, rwxr-x--- and rw-rw-r-- into numbers.

Expected output:

TEXT
== after setting modes ==
755 -rwxr-xr-x deploy.sh
644 -rw-r--r-- app.conf
600 -rw------- id_ed25519
== symbolic changes ==
664 -rw-rw-r-- app.conf (g+w)
660 -rw-rw---- app.conf (o=)
744 -rwxr--r-- deploy.sh (go-x)
== reading a mode string ==
rw-r----- = 640
rwxr-x--- = 750
rw-rw-r-- = 664

Hints

Hint 1: Split the string into three groups of three letters: owner, group, others. Each group becomes one digit.

Approach

Optimal: chmod and stat

Covers: ls -l, the rwx string, octal modes, chmod 755, chmod u+x, chmod g-w, chmod o=, stat -c '%a %A %n'.

Every file answers three questions. Who owns it, which group it belongs to, and what each kind of person may do with it. Linux sorts everyone into three groups:

Who Letter Means
owner u (user) the one user who owns the file
group g every member of the file's group
others o everyone else on the machine

For each of them there are three switches: read (r), write (w) and execute (x).

Reading the string from ls -l. ls -l deploy.sh starts with something like -rwxr-xr-x. That is ten characters: one for the type, then three groups of three.

%%{init: {"flowchart": {"padding": 18, "nodeSpacing": 30, "rankSpacing": 40, "htmlLabels": true}, "themeVariables": {"fontSize": "18px"}}}%% flowchart TB S["-rwxr-xr-x"]:::purple S --> TY["-
type"]:::gray S --> OW["rwx
owner"]:::blue S --> GR["r-x
group"]:::yellow S --> OT["r-x
others"]:::green OW --> D7(["= 7"]):::blue GR --> D5(["= 5"]):::yellow OT --> D5b(["= 5"]):::green classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px

The first character is the type: - for a normal file, d for a folder, l for a link. A - in any other place means "this switch is off".

Turning letters into a number. Each group of three becomes one digit. Add up the switches that are on:

Letter Worth
r read 4
w write 2
x execute 1
- off 0

So rwx = 4 + 2 + 1 = 7, r-x = 4 + 1 = 5, rw- = 4 + 2 = 6 and r-- = 4. That makes rwxr-xr-x the number 755. A few numbers come up again and again:

Number String Typical use
755 rwxr-xr-x scripts and programs, folders
644 rw-r--r-- normal files and configs
600 rw------- private keys, files with passwords
700 rwx------ private folders like ~/.ssh

Two ways to use chmod. Number form sets all three groups at once: chmod 644 app.conf. Letter form changes one part and leaves the rest alone. It reads as who, then + (add), - (remove) or = (set exactly), then which switches:

%%{init: {"flowchart": {"padding": 18, "nodeSpacing": 30, "rankSpacing": 40, "htmlLabels": true}, "themeVariables": {"fontSize": "18px"}}}%% flowchart TB subgraph SYN["chmod who how what"] direction LR WHO["who
u g o a"]:::blue ~~~ OP(["how
+ - ="]):::purple ~~~ WHAT["what
r w x"]:::green end SYN --> EX["chmod g+w app.conf
group: add write"]:::yellow classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px style SYN fill:transparent,stroke:#7c3aed,stroke-width:2px
Command Does
chmod u+x deploy.sh owner: add execute
chmod g+w app.conf group: add write
chmod o= app.conf others: set to nothing
chmod go-x deploy.sh group and others: remove execute
chmod a+r notes.txt all three: add read

Use number form when you know the full result you want. Use letter form when you only want to change one thing, for example "make this script runnable".

What execute means on a folder. On a file, x means "can run it". On a folder, x means "can go inside it", with cd or by opening a file in it. r on a folder lets you list the names in it. That is why folders are usually 755 or 700, never 644: without x, nobody can use the folder at all.

Walking through the code. The # Setup: lines only create the sample files, so skip past them.

  1. chmod 755, 644 and 600 set each file in number form. stat -c '%a %A %n' prints the mode as a number (%a), as a string (%A) and the name (%n). It is easier to read than ls -l, which also prints owner, size and date.
  2. g+w, then o=, then go-x change one part each time. Watch how each line differs from the one before.
  3. The awk loop walks the nine letters, adds 4, 2 or 1 for each letter that is on, and writes a digit after every third letter. It is the same sum you do in your head.

Edge cases. stat -c is GNU (Linux). On macOS the same idea is stat -f '%Lp %N'. Root ignores read and write bits, so a test you run as root may "work" even when the bits say no. Always test permissions as the real user.

# Setup: three files in a fresh temporary folder
cd "$(mktemp -d)"
printf '#!/bin/bash\necho deploying\n' > deploy.sh
printf 'port=8080\n' > app.conf
printf 'FAKE PRIVATE KEY FOR PRACTICE\n' > id_ed25519

# 1. Set modes with numbers: 7 = rwx, 6 = rw-, 5 = r-x, 4 = r--, 0 = ---
chmod 755 deploy.sh
chmod 644 app.conf
chmod 600 id_ed25519
echo "== after setting modes =="
stat -c '%a %A %n' deploy.sh app.conf id_ed25519

# 2. Change one part at a time with letters
echo "== symbolic changes =="
chmod g+w app.conf          # group: add write
stat -c '%a %A %n (g+w)' app.conf
chmod o= app.conf           # others: nothing at all
stat -c '%a %A %n (o=)' app.conf
chmod go-x deploy.sh        # group and others: remove execute
stat -c '%a %A %n (go-x)' deploy.sh

# 3. Turn a permission string into its number
echo "== reading a mode string =="
for mode in rw-r----- rwxr-x--- rw-rw-r--; do
  echo "$mode" | awk '{
    n = 0; out = ""
    for (i = 1; i <= 9; i++) {
      c = substr($0, i, 1)
      if (c == "r") n += 4
      if (c == "w") n += 2
      if (c == "x") n += 1
      if (i % 3 == 0) { out = out n; n = 0 }
    }
    print $0 " = " out
  }'
done
RecapThe whole problem in a few lines, for the night before
  • Spot it: "permission denied", "make the script runnable", "lock down this key"
  • Idea: three groups (owner, group, others) times three switches; r = 4, w = 2, x = 1, add them per group
  • Cost: chmod and stat are instant; -R walks the whole tree
  • Trap: chmod 777 to make an error go away; give the right user or group access instead

Interview follow-ups

  • How do you give a whole team write access to a folder tree, but not everyone?

    Put the files in a shared group, then give that group write access and leave others alone. chgrp -R team /srv/app sets the group, and chmod -R g+w /srv/app adds group write without touching the owner or others bits. Folders also need x for the group, so find /srv/app -type d -exec chmod g+x {} + makes sure the team can enter them. Setting the setgid bit on folders (chmod g+s) makes new files inherit the team group, which the next page explains.

Frequently asked questions

777 turns every switch on for everyone, so of course the error goes away. But it also means any user or any hacked service on the machine can change or replace that file. For a script, that means someone else can change the code you run. The right fix is to find out who needs access, then give only that: make the app user the owner, or add the user to the file's group, and use 750 or 640. If you see 777 in a deploy script, treat it as a bug.

SSH checks the private key's permissions before using it. If the group or others can read it, for example 644, SSH refuses the key, because someone else on the machine could have copied it. Fix it with chmod 600 ~/.ssh/id_ed25519 (or 400). The ~/.ssh folder itself should be 700. This error is very common right after copying a key from another machine or a download.

Number form is best when you know the exact result: "this key must be 600". It is clear and gives the same result every time, whatever the file had before. Letter form is best for a small change that should not touch anything else, like chmod +x script.sh to make a script runnable. In scripts that set up servers, number form is safer, because the final state does not depend on the starting state.