A terminal is the window for text commands; a shell (bash, zsh, PowerShell) is the program that reads and runs them. A developer needs about a dozen commands: change directory (cd), list contents (ls), create and delete files, run node or npm, set an environment variable and chain commands with a pipe. Below are all of these steps with real output and PowerShell equivalents for Windows.
After this you will be able to
- Navigate the file system with
pwd,lsandcdand tell an absolute path from a relative one - Create, copy, rename and delete files and directories without risking your data
- Run
nodeandnpmwith arguments and explain how the shell finds a program through PATH - Set environment variables, chain commands with pipes and redirect output in bash and PowerShell
Know beforehand
Verified with
bash 5.2 (Ubuntu 24.04)Git Bash 5.3 (Git for Windows 2.55)PowerShell 7.6GNU coreutils 9.4Node.js 24.18npm 11.16
Checked against docs: October 11, 2026
Contents
- Terminal, shell and command: what is what
- Windows, macOS and Linux: one idea, different shells
- Navigation: pwd, ls, cd and paths
- Files and directories: mkdir, cp, mv, rm — and why rm -rf is dangerous
- Running programs, PATH and environment variables
- Streams and pipelines: |, >, >>, 2> and grep
- Permissions: chmod and executable scripts
- Practice: create and run a project from the terminal
Terminal, shell and command: what is what
Almost every development tool — Node.js, npm, Git, Docker — works from the command line first. In a graphical interface you click buttons; in a terminal you type short text commands and the computer answers in text. It is faster, more precise and easy to repeat: the same sequence of commands can be saved to a file and run again.
Three words get mixed up. MDN separates them like this: a terminal is the window program that starts a shell and connects to it; the shell is your session with its own settings, and it parses and runs commands; the command line is the line where the cursor blinks. In practice “terminal” and “command line” are used interchangeably, and that is fine.
Terminal
- What it is
- A window that displays text and passes your input to the shell
- Examples
- Terminal on macOS, Windows Terminal, the VS Code integrated terminal
Shell
- What it is
- A program that reads a command, finds the right program and starts it
- Examples
- bash, zsh, PowerShell
Command
- What it is
- A program name or shell built-in plus arguments
- Examples
ls -l src,node index.js --port 3000
Prompt
- What it is
- The text before the cursor: the shell is ready for a command
- Examples
$in bash,PS>in PowerShell
Windows, macOS and Linux: one idea, different shells
Linux always ships a terminal, usually with bash. On macOS the default shell has been zsh since version 10.15 (per Apple Support); the commands in this lesson work the same way there. Windows opens PowerShell by default; for familiar Unix commands people install Git Bash — part of Git for Windows, it emulates bash — or a full Linux through WSL, which MDN recommends.
The key difference in PowerShell: its commands (cmdlets) pass .NET objects to each other, not text. For convenience it has aliases with familiar names: ls, cd, cat, rm call native PowerShell commands. You can check what an alias stands for like this:
PS> Get-Command ls, cd, pwd, cat, rm, cp, mv | Select-Object Name, Definition
Name Definition
---- ----------
ls Get-ChildItem
cd Set-Location
pwd Get-Location
cat Get-Content
rm Remove-Item
cp Copy-Item
mv Move-ItemWhere am I
- bash
pwd- PowerShell 7
Get-Location(pwd)
List files
- bash
ls -la- PowerShell 7
Get-ChildItem -Force(ls)
Change directory
- bash
cd src- PowerShell 7
Set-Location src(cd)
Create a directory
- bash
mkdir -p a/b- PowerShell 7
New-Item -ItemType Directory a/b
Create an empty file
- bash
touch README.md- PowerShell 7
New-Item README.md -ItemType File
Show a file
- bash
cat file.txt- PowerShell 7
Get-Content file.txt(cat)
Copy / move
- bash
cp a b/mv a b- PowerShell 7
Copy-Item a b/Move-Item a b
Delete a directory with contents
- bash
rm -r dir- PowerShell 7
Remove-Item -Recurse dir
Find lines
- bash
grep api access.log- PowerShell 7
Select-String api access.log
Count lines
- bash
… | wc -l- PowerShell 7
(… | Measure-Object).Count
Environment variable
- bash
export APP_ENV=test- PowerShell 7
$env:APP_ENV = "test"
Where a program lives
- bash
command -v node- PowerShell 7
(Get-Command node).Source
Navigation: pwd, ls, cd and paths
The shell is always “in” some directory — the current working directory. At startup it is your home directory. pwd shows where you are, ls shows what is around, cd moves to another directory. mkdir -p creates a directory together with any missing parents.
$ pwd
/home/ubuntu
$ mkdir -p projects/hello/src
$ cd projects/hello
$ pwd
/home/ubuntu/projects/hello
$ ls
src
$ cd src
$ pwd
/home/ubuntu/projects/hello/src
$ cd ..
$ cd ../..
$ pwd
/home/ubuntu
$ cd /home/ubuntu/projects/hello/src
$ cd ~
$ pwd
/home/ubuntu- An absolute path starts at the root
/:/home/ubuntu/projects/hello/srcleads to the same place wherever you are. - A relative path starts from the current directory:
cd srcworks only ifsrcis right here. ..is the parent directory,.the current one,~your home.cdwith no arguments also takes you home.- The Tab key completes a file or directory name once you have typed the beginning: fewer typos, less typing.
- Windows paths use backslashes (
C:\Users\name), while Git Bash shows the same drive as/c/Users/name.
Files and directories: mkdir, cp, mv, rm — and why rm -rf is dangerous
touch creates an empty file, cp copies, mv moves or renames: a rename is a move into the same directory under a new name. rm deletes a file, rmdir deletes only an empty directory, and rm -r deletes a directory with everything inside. ls -l gives a detailed listing: permissions, owner, size and date.
$ cd ~/projects/hello
$ touch README.md
$ echo 'console.log("hello")' > src/index.js
$ cat src/index.js
console.log("hello")
$ cp src/index.js src/index.backup.js
$ mv src/index.backup.js old.js
$ ls -l . src
.:
total 8
-rw-r--r-- 1 ubuntu ubuntu 0 Oct 11 22:02 README.md
-rw-r--r-- 1 ubuntu ubuntu 21 Oct 11 22:02 old.js
drwxr-xr-x 2 ubuntu ubuntu 4096 Oct 11 22:02 src
src:
total 4
-rw-r--r-- 1 ubuntu ubuntu 21 Oct 11 22:02 index.js
$ rm old.js
$ rmdir src
rmdir: failed to remove 'src': Directory not empty
$ rm -r src
$ ls
README.md- Before deleting, run
pwdandlswith the same path — make sure you see exactly what you are about to remove. - For manual work use
rm -i: the shell asks for confirmation for each file. - Do not copy commands with
rm -rfandsudofrom the internet until you understand every part. - Run risky experiments in a separate test directory or in a container.
Running programs, PATH and environment variables
When you type node index.js --port 3000, the first word is the program name and the rest are arguments: the program receives them as a list and decides what to do with them. Flags usually start with one dash (-l) or two (--port). If the name has no slash, the shell looks for the program in the directories listed in the PATH environment variable — in order, until it finds an executable file. That is how POSIX describes it; on Linux and macOS the directories in PATH are separated by colons, on Windows by semicolons.
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
$ command -v bash ls
/usr/bin/bash
/usr/bin/ls
$ type cd
cd is a shell builtin
$ node --version
bash: node: command not found
$ echo $?
127command -v shows which file will run, and type identifies shell built-ins: cd is not a program but part of the shell itself. The exit status is stored in $?: 0 means success, any other number is an error. POSIX assigns 127 to “command not found” and 126 to “found but not executable”. A command not found message nearly always means one of two things: the program is not installed, or its directory is not in PATH (restarting the terminal after installation often helps).
Environment variables are name=value pairs a process inherits from its parent at startup. They pass settings to programs without changing code: the mode, the port, the database address. A plain shell variable is visible only to the shell itself; export makes it part of the environment of child processes. Writing APP_ENV=test command sets the variable for that one command only.
$ echo $HOME
/home/ubuntu
$ GREETING=hello
$ echo $GREETING
hello
$ bash -c 'echo "child: [$GREETING]"'
child: []
$ export GREETING
$ bash -c 'echo "child: [$GREETING]"'
child: [hello]
$ APP_ENV=test bash -c 'echo "APP_ENV=$APP_ENV"'
APP_ENV=test
$ echo "after: [$APP_ENV]"
after: []
$ unset GREETINGIn PowerShell environment variables are available as $env:NAME. Microsoft's documentation notes that such a change affects only the current session and is inherited by child processes; for a permanent value you put the variable in your PowerShell profile or system settings. PowerShell has no APP_ENV=test command form — set the variable on a separate line.
PS> $env:APP_ENV = "test"
PS> pwsh -NoProfile -c '"child: [$env:APP_ENV]"'
child: [test]
PS> Remove-Item Env:APP_ENV
PS> "[$env:APP_ENV]"
[]
PS> $env:PATH -split ";" | Select-String nodejs
C:\Program Files\nodejsStreams and pipelines: |, >, >>, 2> and grep
Every program has three standard streams: input (stdin, number 0), output (stdout, 1) and errors (stderr, 2). By default output and errors go to the screen. The pipe | connects one command's output to the next command's input — as POSIX puts it, the standard output of command1 is connected to the standard input of command2. This is how small utilities combine into the tool you need: grep filters lines, wc -l counts them, sort sorts them.
$ printf 'GET /\nPOST /api/lead\nGET /about\nGET /api/health\n' > access.log
$ grep api access.log
POST /api/lead
GET /api/health
$ grep api access.log | wc -l
2
$ grep api access.log > api.txt
$ echo 'GET /api/price' >> api.txt
$ cat api.txt
POST /api/lead
GET /api/health
GET /api/price
$ ls missing.txt
ls: cannot access 'missing.txt': No such file or directory
$ echo $?
2
$ ls missing.txt 2> errors.txt
$ cat errors.txt
ls: cannot access 'missing.txt': No such file or directory
$ mkdir demo && cd demo && echo "created and entered"
created and entered
$ cd ~
$ ls missing.txt && echo "never printed" || echo "ls failed, exit code $?"
ls: cannot access 'missing.txt': No such file or directory
ls failed, exit code 2a | b
- What it does
- The output of
abecomes the input ofb
a > file
- What it does
- Write output to a file, overwriting it
a >> file
- What it does
- Append output to the end of a file
a 2> file
- What it does
- Write only errors to a file
a > file 2>&1
- What it does
- Write both output and errors to a file
a && b
- What it does
- Run
bonly ifasucceeded (exit code 0)
a || b
- What it does
- Run
bonly ifafailed
>, >>, 2>, |, && and || also work in PowerShell 7 — the last two arrived in version 7. But PowerShell pipelines carry objects, so instead of grep and wc you use Select-String and Measure-Object. A classic beginner mistake is > instead of >>: one extra angle bracket silently wipes the file.
Permissions: chmod and executable scripts
On Linux and macOS every file has permissions for three classes: the owner, the group and everyone else. Each class gets read (r), write (w) and execute (x). In ls -l output the first character is the type (d for a directory, - for a file), followed by three triplets of permissions. You can run a script as ./build.sh only if it has the x bit; otherwise the shell answers Permission denied.
$ printf '#!/usr/bin/env bash\necho "Build started in $(pwd)"\n' > build.sh
$ ls -l build.sh
-rw-r--r-- 1 ubuntu ubuntu 51 Oct 11 22:02 build.sh
$ ./build.sh
bash: ./build.sh: Permission denied
$ chmod +x build.sh
$ ls -l build.sh
-rwxr-xr-x 1 ubuntu ubuntu 51 Oct 11 22:02 build.sh
$ ./build.sh
Build started in /home/ubuntu
$ chmod 600 build.sh
$ ls -l build.sh
-rw------- 1 ubuntu ubuntu 51 Oct 11 22:02 build.shchmod +x build.sh
- Permissions
- Add execute for everyone
- When to use
- Your own script you want to run as
./build.sh
chmod 755 (rwxr-xr-x)
- Permissions
- Owner — everything, others — read and execute
- When to use
- Scripts and directories others may use
chmod 644 (rw-r--r--)
- Permissions
- Owner — read and write, others — read
- When to use
- Regular project files
chmod 600 (rw-------)
- Permissions
- Owner only, no execute
- When to use
- Private files: keys,
.envon a server
The digits are octal: r = 4, w = 2, x = 1, summed for each class. The first line of the script, #!/usr/bin/env bash (the shebang), tells the system which program should run it. Without the x bit you can still run a script explicitly: bash build.sh.
Practice: create and run a project from the terminal
Let us put it all together: create a project directory, initialise npm, write a script that reads its arguments and an environment variable, and run it three ways. You need Node.js installed. In Node.js command-line arguments are available as process.argv and environment variables as process.env.
const name = process.env.USER_NAME ?? "world";
const args = process.argv.slice(2);
console.log(`Hello, ${name}!`);
console.log(`Arguments: ${args.join(" ") || "none"}`);$ node --version
v24.18.0
$ npm --version
11.16.0
$ mkdir -p ~/projects/hello-cli && cd ~/projects/hello-cli
$ npm init -y > /dev/null
$ ls
package.json
$ node index.js
Hello, world!
Arguments: none
$ node index.js --port 3000
Hello, world!
Arguments: --port 3000
$ USER_NAME=Asel node index.js
Hello, Asel!
Arguments: none
$ npm pkg set scripts.start="node index.js"
$ npm start -- --verbose
> hello-cli@1.0.0 start
> node index.js --verbose
Hello, world!
Arguments: --verbosels and node index.js steps).The double dash in npm start -- --verbose is a boundary: npm does not parse anything after it and passes it to the script, as the npm run documentation describes. The same scenario in PowerShell 7:
PS> New-Item -ItemType Directory hello-cli -Force | Out-Null
PS> Set-Location hello-cli
PS> npm init -y > $null
PS> node index.js --port 3000
Hello, world!
Arguments: --port 3000
PS> $env:USER_NAME = "Asel"
PS> node index.js
Hello, Asel!
Arguments: none
PS> Remove-Item Env:USER_NAME
PS> npm pkg set scripts.start="node index.js"
PS> npm start -- --verbose
> hello-cli@1.0.0 start
> node index.js --verbose
Hello, world!
Arguments: --verbosenode --versionprints a version, notcommand not found- Without the variable the script prints
Hello, world!; withUSER_NAME=Aselit printsHello, Asel! - Arguments after the file name appear in the
Arguments:line package.jsonnow has astartscript, andnpm startruns it
The next step is Git: it runs in the same terminal, and confident navigation between directories is needed from the very first command.
How this was verified5 · passedchecked 11 October 2026
“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.
Sources
- MDN — Command line crash course
- POSIX.1-2024 — Shell Command Language (pipelines, redirection, AND/OR lists, command search)
- POSIX.1-2024 — Environment Variables (PATH, HOME)
- Microsoft Learn — about_Environment_Variables (PowerShell)
- Microsoft Learn — about_Pipelines (PowerShell)
- Apple Support — Use zsh as the default shell on Mac