
A student in our June batch spent an hour trying to run a Python script on a Linux server. The script was right there in the directory. ls -l showed it. But every time he typed python script.py, the shell said "Permission denied." He thought the file was corrupt. It wasn't. He just hadn't made it executable.
That mistake is common. It happens because most tutorials teach Linux as a list of commands to memorize, not as a system with rules. Once you understand four underlying ideas, the commands stop being random and start making sense.
The shell is a programming language, not a file manager
When you open a terminal, you're not "looking at files." You're running a program called a shell, and the shell interprets what you type. Every command you enter is either a built-in function of the shell or an external program installed somewhere on your system.
Take ls. It's a real binary file, usually at /bin/ls. When you type ls, the shell searches a list of directories called PATH and runs the first ls it finds. You can check where a command lives with which:
$ which ls
/bin/ls
$ which python
/usr/bin/python
This is why you sometimes see errors like command not found even though you're sure the program is installed. The program's directory isn't in PATH. Or the program isn't installed at all—which is more common than people admit.
The shell also has its own syntax: variables, loops, conditionals, functions. You can write a for loop directly in the terminal without creating a script file. That's not a trick; it's the shell being a full scripting language. Most developers who migrate from Windows or macOS never treat the shell as a language, so they copy-paste commands without understanding how they compose.
When we tell students to write small shell scripts instead of running commands one by one, they start seeing the shell as what it is: an interface to everything on the machine.
File permissions are your first security wall
Every file and directory on Linux has an owner, a group, and a permission string. That string looks like -rwxr-xr--. The first character is the type: - for a regular file, d for a directory, l for a symbolic link. The next nine characters are three sets of three: owner, group, and everyone else. Each set has r for read, w for write, x for execute.
For a file, x means "run this as a program." For a directory, x means "traverse into it." So a directory with only r but no x can be listed but you can't cd into it. That trips people up constantly.
Here's the practical part. When you create a script, it's usually not executable by default. You have to change it with chmod:
$ chmod +x script.py
$ ls -l script.py
-rwxr-xr-x 1 student students 1024 Jun 12 09:15 script.py
The +x adds execute permission. You can also set permissions with numbers: chmod 755 means owner can read/write/execute, group and others can read/execute. The numbers are additive: read is 4, write is 2, execute is 1. 7 is 4+2+1, 5 is 4+1.
There's a subtlety: the x bit on a file does not mean "this file is safe to run." It means "the kernel will allow you to try." Whether the file actually runs depends on its shebang line (like #!/usr/bin/env python3) or on the binary format. A text file with x set but no shebang will give you an error.
Also, chmod only works if you own the file or have root access. If you see Permission denied and you're not the owner, chmod won't help. You need chown (change owner) or sudo. And if you're on a shared server, ask the administrator first. Don't guess.
Standard streams keep everything flowing
Every running program on Linux has three open channels by default: standard input (stdin), standard output (stdout), and standard error (stderr). When you run a program in the terminal, stdin is your keyboard, stdout is your screen, and stderr is also your screen. The distinction between stdout and stderr matters because they can be redirected separately.
Redirection uses > and <. > sends stdout to a file, overwriting it. >> appends instead. 2> redirects stderr. And | (the pipe) sends stdout of one command to stdin of the next.
A concrete example: you want a list of all running processes that mention "nginx," but you don't want the header line. You can pipe ps into grep:
$ ps aux | grep nginx | grep -v grep
The first grep filters for "nginx." The second grep -v grep removes the line that matches your own grep command—because grep nginx itself appears in the process list. Every Linux user eventually learns this trick the hard way.
Redirection is where beginners get burned. If you type > by mistake and overwrite a config file, it's gone. There's no undo in the shell. We tell students to test redirection with a throwaway file first. echo "test" > temp.txt is safe. echo "test" > /etc/nginx/nginx.conf is not.
Pipes are the real power. They let you compose small tools into larger ones. cat for reading, grep for filtering, sort for sorting, uniq for deduplication. Each does one thing well, and the pipe connects them. That's the Unix philosophy, and it still works decades later.
Processes and job control
When you run a command, the shell creates a process. That process has a PID (process ID). You can see running processes with ps or top. If a program hangs, you can kill it with kill and its PID. But kill doesn't always mean "destroy." By default it sends a SIGTERM signal, asking the program to terminate gracefully. If the program ignores that, you can send SIGKILL with kill -9, which the program cannot catch.
Background jobs are another source of confusion. If you run a long command and want your terminal back, append &:
$ python server.py &
[1] 12345
The shell prints the job number and PID. The process runs in the background, and you can keep typing. But there's a catch: when you close the terminal, the background process gets a SIGHUP signal and may terminate. To keep it running after logout, you need nohup or a process manager like systemd. That's not a beginner topic, but it's good to know the question exists.
Jobs themselves can be suspended with Ctrl+Z, then resumed in the foreground with fg or background with bg. If you have multiple jobs, jobs lists them. This is useful when you're in a terminal without a graphical interface and you accidentally start a huge command while editing a file.
One thing we don't know: whether your Linux server runs systemd or an older init system. It affects how you manage services. Ask the person who set up the server, or check with ps -p 1. The answer changes your workflow.
Common questions
How do I find a file if I don't know where it is?
Use find /path -name "filename". For example, find /etc -name "*.conf" lists every .conf file under /etc. If you search the whole disk, prepend sudo to avoid permission errors. The locate command is faster but relies on a database that may be outdated; run sudo updatedb first.
What's the difference between a symbolic link and a hard link?
A symbolic link (created with ln -s) is a pointer to another file's path. If you move or delete the target, the link breaks. A hard link (created with ln without -s) points to the same data on disk as the original file; deleting the original doesn't remove the data, because the link still references it. Hard links only work on the same filesystem, and you can't hard-link directories.
Why do I need to type ./script.sh instead of just script.sh?
Because the current directory (.) is not in your PATH by default. The shell only searches the directories listed in PATH. Adding . to PATH is possible but a security risk: a malicious script in your current directory could be executed by accident when you type a common command. The ./ prefix is explicit and safe.
How do I permanently add a directory to my PATH?
Add an export PATH="$PATH:/your/directory" line to your shell's startup file. For bash, that's ~/.bashrc. For zsh, it's ~/.zshrc. Then run source ~/.bashrc or open a new terminal. The change is per-user, not system-wide. If you need to change it for all users, edit /etc/environment or add a file in /etc/profile.d/, but that requires root access.
Courses this relates to
Come and sit a class before you decide
Reading about it only gets you so far. Pick a day, sit in on a class that is already running, write some code, and talk to the students in it. Free, and nothing to pay afterwards unless you want to join.