In bash, `sudo echo hello > /etc/motd` fails with "Permission denied" even though the user has full sudo rights. Which process is doing the redirection, and how do you write that file as root?
answer
- who opens the file
- sudo elevates the command, not the line
- the error names bash, not sudo
- no password prompt is a clue
- pipe into a privileged writer
basics
~10 sYour unprivileged shell performs the redirection, opening /etc/motd as you before sudo ever runs, so the open fails. Pipe into a privileged writer instead: echo hello | sudo tee /etc/motd.
solid answer
~50 s`sudo` elevates the command it is given; it has nothing to do with the redirection, because the redirection is the shell's job and happens first. Your shell parses the line, tries to open `/etc/motd` for writing under your own UID, gets EACCES, and reports `bash: /etc/motd: Permission denied` — often without sudo running at all, which is why you may not even be prompted for a password. The usual fix is to move the file-opening into a privileged process: `echo hello | sudo tee /etc/motd > /dev/null`. Here `tee` runs as root, reads the pipe from your shell, and opens the file itself; `> /dev/null` just suppresses tee's copy on stdout, and `tee -a` appends instead of truncating. `sudo sh -c 'echo hello > /etc/motd'` also works because the redirection then happens inside the root shell, but it re-parses a string, so it needs careful quoting.
go deeper
Know the working incantation and why it exists: the redirect is your shell's, so use echo x | sudo tee file. Remember -a when you mean append rather than replace.
Explain the order — the shell opens the target as you, then execs sudo — and point at the error message naming bash as the evidence. Contrast the tee form with sudo sh -c and say what each one elevates.
Bring the consequences: a truncated /etc/hosts from a missing -a, log files silently owned by the wrong user, and the injection risk of building a sudo sh -c string from variables. Prefer install or a config-management tool for anything recurring.
Question the shape of the task rather than the syntax: routinely hand-editing privileged files from interactive shells is the smell. Decide where machine state is owned — configuration management, an image build, a package — and reserve ad-hoc elevation for break-glass work with an audit trail.
## What sudo actually elevates `sudo` is an ordinary program. Your shell starts it as you, sudo checks its policy, changes its own credentials to the target user, and then execs the command you asked for. Everything sudo affects happens *inside that new process*. Anything the shell did while building the command line happened earlier, as you. Redirection is one of those earlier things. For `sudo echo hello > /etc/motd`, bash: 1. splits and expands the words; 2. strips `> /etc/motd` from the command line and opens that path for writing — **as your unprivileged user**; 3. only then execs `sudo` with the arguments `echo hello`. Step 2 fails on a root-owned file, so step 3 never happens. The tell is in the error message: `bash: /etc/motd: Permission denied` names the *shell*, and you usually get no sudo password prompt at all, because sudo was never started. ## Moving the open into a privileged process The fix is always the same shape: make a process that already has the privilege do the opening. ```bash echo "Welcome" | sudo tee /etc/motd > /dev/null printf '%s\n' "extra line" | sudo tee -a /etc/motd > /dev/null ``` `tee` copies its standard input to each file it is given *and* to its own standard output. Under `sudo` it runs as root, so its `open()` on `/etc/motd` succeeds. Two details are worth knowing: - **`> /dev/null` is cosmetic, not security.** Without it, everything you wrote is echoed back on your terminal. In a script that is noise; in a pipeline it is corruption of the data channel. - **`tee` truncates by default.** `sudo tee /etc/hosts` replaces the file with whatever came down the pipe. Appending a line needs `sudo tee -a`. This is the single most expensive mistake in the pattern, and the reason to think before pasting one from a blog post. `tee` is not the only privileged writer. `sudo dd of=/etc/motd`, `sudo install -m 0644 /dev/stdin /etc/motd`, or `sudo cp staged.conf /etc/app.conf` all put the open inside the elevated process, and `install` additionally sets ownership and mode in one step. ## The `sudo sh -c` alternative ```bash sudo sh -c 'echo hello > /etc/motd' ``` This works because sudo starts a root shell, and *that* shell performs the redirection with root's credentials. It is often the clearest option when you need several redirections or a small compound command. The cost is that you are handing a string to a shell to re-parse. Any value interpolated into it is now shell source code running as root, so a filename or user-supplied variable spliced in unquoted is a straightforward command-injection hole. If a variable must reach it, pass it as a positional argument rather than pasting it into the script text: ```bash sudo sh -c 'printf "%s\n" "$1" > /etc/motd' _ "$message" ``` (The `_` fills `$0`.) The general principle — never let untrusted text become part of a command string — is worth stating explicitly to an interviewer here. ## Related traps in the same family The "the shell did it, not the command" rule explains several other surprises: - `sudo cat secret.txt > mine.txt` works for reading the protected file (cat is elevated) but writes `mine.txt` as *you* — usually what you want, and occasionally not. - `sudo command > /var/log/thing.log` creates the log owned by your user, not root, because your shell created it. - The same applies with `ssh`: `ssh host cmd > out` runs `cmd` remotely but redirects on your local machine. - Under `sudo -u other`, an output file created by your shell is still yours; only files the elevated command opens belong to the target user. ## Exit status One caveat to keep in the back of your mind: `echo x | sudo tee f` is a pipeline, so the status you get back is `tee`'s. Checking whether the write really succeeded means being deliberate about how a pipeline reports failure — a topic in its own right, but worth flagging rather than assuming a zero status proves the file was written.
- Why does `sudo tee` need `> /dev/null` in most one-liners?Because tee copies its input to every named file and to its own standard output. Without the redirection, everything you wrote is echoed back on your terminal, which is noise interactively and outright pollution if the command sits inside a pipeline or a script whose stdout is the data channel. It has no bearing on the file being written.
- What is the risk in `sudo sh -c "echo $msg > /etc/motd"` with double quotes?`$msg` is expanded by your shell into a string that a root shell then parses as source code. A value containing `;` or backticks becomes a command running as root. Use single quotes and pass the value as a positional argument — `sudo sh -c 'printf "%s\n" "$1" > /etc/motd' _ "$msg"` — so it is data, never program text.
- Who owns the file created by `sudo mycmd > /var/log/out.log`?You do. Your shell creates and opens the file before sudo starts, so it is created with your UID and your umask; sudo only elevates mycmd, which inherits an already-open descriptor. If root ownership matters, redirect inside the elevated process — `sudo sh -c 'mycmd > /var/log/out.log'` — or pipe through `sudo tee`.
saying these in an interview costs you the question
- Claiming sudo drops the redirection when elevating
- Thinking echo itself lacks permission to write
- Using sudo tee without -a and truncating a config
- Assuming sudo ran because the command failed
- Interpolating variables into sudo sh -c strings