01Shell basics
A shell reads what you type, expands it, and runs it. Know these and the rest follows.
echo $SHELL # which shell you're running (e.g. /bin/bash) type -a ls # is it a builtin, alias, or program? show all which python3 # full path of an executable in $PATH 'literal $HOME' # single quotes: no expansion (prints $HOME) "expanded $HOME" # double quotes: variables expand echo file_*.log # globbing: * ? [abc] match filenames cmd1; cmd2 # run in sequence; cmd1 && cmd2 = only if first ok !! # repeat the last command (sudo !! is handy) !$ # last argument of the previous command man ss # or: ss --help # the manual / usage — first stop for any flag
$ type -a ls ls is aliased to `ls --color=auto' ls is /usr/bin/ls
02Environment variables — manipulate them
A variable is a name holding a value. Export it and child processes inherit it — that's how you configure most CLIs.
exported variable = name + value + export
NAME=alban # shell variable (this shell only)
export NAME # promote it to an environment variable
export EDITOR=vim # set + export in one line
echo "$NAME" # read it (quote to be safe with spaces)
printenv PATH # print one environment variable
env | sort | less # list the whole environment
AWS_PROFILE=prod aws s3 ls # one-shot: set a var for a single command only
: "${REGION:=eu-central-1}" # default a var if unset (parameter expansion)
echo "${NAME:-guest}" # use a fallback without assigning
unset NAME # remove a variable
export PATH="$HOME/bin:$PATH" # prepend a dir to PATH (order = priority)
$ printenv PATH /home/alban/.local/bin:/usr/local/bin:/usr/bin:/bin
03Make variables persist
Variables vanish when the shell closes. Put exports in the right startup file to keep them.
~/.bashrc / ~/.zshrc # per-user, interactive shells (most edits go here) ~/.profile / ~/.bash_profile # per-user, login shells /etc/environment # system-wide, all users (KEY=value, no 'export') source ~/.bashrc # reload without logging out (or: . ~/.bashrc) # Example line to add to ~/.bashrc: export PATH="$HOME/.local/bin:$PATH"
04Handling secrets safely — best practices
Environment variables are inherited by every child process and are readable via
ps e and /proc/<pid>/environ, so a secret in your environment is not
really hidden. Treat credentials with care — set this up before touching the cloud
CLIs below.
- Never hardcode or commit secrets. Gitignore
.env,~/.aws/credentials, and*.pem; scan for leaks withgitleaksorgit-secretsin a pre-commit hook. - Use the tool's own credential store, not raw keys —
aws configure/aws configure sso, andaz loginor a managed identity. Lock file permissions. - Prefer short-lived credentials — STS
assume-role, or OIDC federation in CI — over long-lived static keys. - Use a secret manager / vault — AWS Secrets Manager, Azure Key Vault, or
HashiCorp Vault — and fetch at runtime. For local dev, a gitignored
.envloaded bydirenv, or an OS keychain /pass. - Keep secrets out of history and listings, rotate them, and grant least privilege.
- In CI/CD, use the platform's encrypted secrets (e.g. GitHub Actions secrets), never plaintext in the workflow file.
chmod 600 ~/.aws/credentials # only you can read the credential file aws configure sso # browser-based login, short-lived tokens export HISTCONTROL=ignorespace # commands starting with a space skip history export TOKEN=... # (note the leading space → not saved) git secrets --install && git secrets --register-aws # block AWS keys in commits gitleaks detect --source . # scan a repo for committed secrets direnv allow # auto-load a gitignored .env in this dir only aws sts assume-role \ # get temporary, scoped credentials --role-arn arn:aws:iam::123456789012:role/deploy \ --role-session-name cli unset AWS_SECRET_ACCESS_KEY # clear a secret from the environment when done
$ gitleaks detect --source . 9:14AM INF scanned ~128 commits 9:14AM INF no leaks found
05Networking on the CLI
Most CLIs and package managers obey the proxy environment variables — essential behind a corporate egress. For the full networking toolkit, see the Linux commands guide.
export http_proxy=http://proxy.example.com:8080 # HTTP via a proxy export https_proxy=http://proxy.example.com:8080 # HTTPS via a proxy export no_proxy=localhost,127.0.0.1,10.0.0.0/8 # bypass the proxy for these curl -I https://example.com # check reachability + headers (obeys *_proxy) curl -x http://proxy.example.com:8080 https://example.com # one-off explicit proxy ip -br a # local interfaces + addresses (quick view) ss -tulpn # listening TCP/UDP ports + owning PIDs dig +short A example.com # resolve a name (is DNS the problem?)
$ curl -I https://example.com HTTP/2 200 content-type: text/html; charset=UTF-8 server: nginx
06AWS on the CLI (aws)
Configure once, switch contexts with a profile and region, and confirm who you are before you run anything that changes state.
AWS static credentials = access key ID + secret access key (+ session token)
aws configure # interactive: writes ~/.aws/{config,credentials}
aws configure sso # preferred: SSO login, temporary credentials
export AWS_PROFILE=prod # pick a named profile for this shell
export AWS_REGION=eu-central-1 # region (AWS_DEFAULT_REGION also works)
# Static keys are a last resort — placeholders below are AWS's public examples:
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
aws sts get-caller-identity # who am I? (account + ARN) — always verify first
aws s3 ls # list buckets
aws ec2 describe-instances \ # servers, as a table
--query 'Reservations[].Instances[].[InstanceId,State.Name,PrivateIpAddress]' \
--output table
aws configure list-profiles # what profiles are available
$ aws sts get-caller-identity
{
"UserId": "AIDAEXAMPLE0PLACEHOLDER",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/alban"
}
07Azure on the CLI (az)
Log in interactively for day-to-day work; use a service principal's env vars only for automation, and prefer a managed identity where you can.
Azure SP login = tenant ID + client ID + client secret
az login # browser sign-in (device code: az login --use-device-code) az account show # current subscription + tenant az account list -o table # all subscriptions you can see az account set --subscription "Prod" # switch active subscription # Service-principal env vars (automation) — placeholder GUIDs: export AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000 export AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000 export AZURE_CLIENT_SECRET='' export AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 az group list -o table # resource groups az vm list -d -o table # VMs with power state az vm list --query "[].{name:name, rg:resourceGroup}" -o table # shape the output
$ az account show -o table Name CloudName SubscriptionId State IsDefault ------ ----------- ------------------------------------ ------- ----------- Prod AzureCloud 00000000-0000-0000-0000-000000000000 Enabled True
08Where to go next
Once these commands are muscle memory, the next step is to stop typing them: wrap the repeatable ones in scripts, feed credentials from a vault, and let a pipeline run them. That's the jump from a shell session to driving APIs and infrastructure as code. Back to the Knowledge Base.