The AWS Console is fine for exploration. For real work—auditing, automation, bulk operations—the CLI is essential. Here’s how to use it effectively.

Output Formats

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# JSON (default, best for scripting)
aws ec2 describe-instances --output json

# Table (human readable)
aws ec2 describe-instances --output table

# Text (tab-separated, grep-friendly)
aws ec2 describe-instances --output text

# YAML
aws ec2 describe-instances --output yaml

Set default in ~/.aws/config:

1
2
3
[default]
output = json
region = us-east-1

JMESPath Queries

The --query flag uses JMESPath to filter and transform output:

Basic Selection

1
2
3
4
5
6
7
8
# Get all instance IDs
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].InstanceId'

# Get specific fields
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId, State.Name, InstanceType]' \
  --output table

Filtering

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Running instances only
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?State.Name==`running`].InstanceId'

# Instances with specific tag
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?Tags[?Key==`Environment` && Value==`production`]].InstanceId'

# Multiple conditions
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?State.Name==`running` && InstanceType==`t3.micro`].[InstanceId, Tags[?Key==`Name`].Value | [0]]'

Renaming Fields

1
2
3
4
# Create named objects
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].{ID: InstanceId, Type: InstanceType, State: State.Name}' \
  --output table

Sorting and Limiting

1
2
3
4
5
6
7
# Sort by launch time
aws ec2 describe-instances \
  --query 'sort_by(Reservations[].Instances[], &LaunchTime)[*].[InstanceId, LaunchTime]'

# First 5 results
aws ec2 describe-instances \
  --query 'Reservations[].Instances[][:5].InstanceId'

Server-Side Filtering

Use --filters when possible—it’s faster than client-side --query:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Filter on AWS side
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
            "Name=tag:Environment,Values=production"

# Combine with query for output shaping
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].[InstanceId, Tags[?Key==`Name`].Value | [0]]' \
  --output table

Pagination

Large result sets are paginated. Handle it:

1
2
3
4
5
6
7
8
# Automatic (default, but can be slow)
aws s3api list-objects-v2 --bucket my-bucket

# Manual pagination
aws s3api list-objects-v2 --bucket my-bucket --max-items 100

# Get everything, no pagination limits
aws s3api list-objects-v2 --bucket my-bucket --no-paginate

Waiters

Wait for resources to reach a state:

1
2
3
4
5
6
7
8
# Wait for instance running
aws ec2 wait instance-running --instance-ids i-1234567890abcdef0

# Wait for stack complete
aws cloudformation wait stack-create-complete --stack-name my-stack

# With timeout (in separate terminal or script)
timeout 300 aws ec2 wait instance-running --instance-ids i-123...

Profiles and Roles

Multiple Accounts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# ~/.aws/credentials
[default]
aws_access_key_id = AKIA...
aws_secret_access_key = ...

[prod]
aws_access_key_id = AKIA...
aws_secret_access_key = ...

[dev]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
1
2
aws s3 ls --profile prod
aws ec2 describe-instances --profile dev

Role Assumption

1
2
3
4
5
# ~/.aws/config
[profile prod-admin]
role_arn = arn:aws:iam::123456789012:role/AdminRole
source_profile = default
region = us-east-1
1
aws sts get-caller-identity --profile prod-admin

SSO

1
2
aws configure sso
aws sso login --profile my-sso-profile

Common Patterns

Find Resources by Tag

1
2
3
4
# All resources with specific tag
aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=Environment,Values=production \
  --query 'ResourceTagMappingList[].ResourceARN'

Bulk Operations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Stop all dev instances
aws ec2 describe-instances \
  --filters "Name=tag:Environment,Values=dev" "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].InstanceId' \
  --output text | xargs -n 1 aws ec2 stop-instances --instance-ids

# Delete old snapshots
aws ec2 describe-snapshots --owner-ids self \
  --query "Snapshots[?StartTime<='2024-01-01'].SnapshotId" \
  --output text | xargs -n 1 aws ec2 delete-snapshot --snapshot-id

Get Account Info

1
2
3
4
5
6
7
8
# Current identity
aws sts get-caller-identity

# Account ID only
aws sts get-caller-identity --query Account --output text

# Current region
aws configure get region

S3 Operations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Sync directory
aws s3 sync ./dist s3://my-bucket/

# Sync with delete
aws s3 sync ./dist s3://my-bucket/ --delete

# Copy with metadata
aws s3 cp file.txt s3://bucket/ --metadata '{"key":"value"}'

# Presigned URL (temporary access)
aws s3 presign s3://bucket/file.txt --expires-in 3600

# Bucket size
aws s3 ls s3://my-bucket --recursive --summarize | tail -2

CloudWatch Logs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Tail logs
aws logs tail /aws/lambda/my-function --follow

# Filter logs
aws logs filter-log-events \
  --log-group-name /aws/lambda/my-function \
  --filter-pattern "ERROR" \
  --start-time $(date -d '1 hour ago' +%s000)

# Get recent logs
aws logs get-log-events \
  --log-group-name /aws/lambda/my-function \
  --log-stream-name 'latest-stream' \
  --limit 50

Lambda

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Invoke function
aws lambda invoke \
  --function-name my-function \
  --payload '{"key": "value"}' \
  response.json

# Update function code
aws lambda update-function-code \
  --function-name my-function \
  --zip-file fileb://function.zip

# View recent invocations
aws lambda get-function \
  --function-name my-function \
  --query 'Configuration.[FunctionName, LastModified, MemorySize, Timeout]'

EC2 SSH Helper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Get public IP for instance
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text

# SSH function
ec2ssh() {
  IP=$(aws ec2 describe-instances --instance-ids "$1" \
    --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
  ssh ec2-user@$IP
}

Cost Explorer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Current month spend
aws ce get-cost-and-usage \
  --time-period Start=$(date +%Y-%m-01),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --query 'ResultsByTime[0].Total.BlendedCost'

# By service
aws ce get-cost-and-usage \
  --time-period Start=2024-01-01,End=2024-02-01 \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query 'ResultsByTime[0].Groups[*].[Keys[0], Metrics.BlendedCost.Amount]' \
  --output table

Shell Integration

Aliases

1
2
3
4
5
6
# ~/.bashrc
alias awswho='aws sts get-caller-identity'
alias awsregion='aws configure get region'

# Quick instance list
alias ec2ls='aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId, State.Name, Tags[?Key==\`Name\`].Value | [0], InstanceType, PrivateIpAddress]" --output table'

Functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Switch profiles
awsp() {
  export AWS_PROFILE=$1
  aws sts get-caller-identity
}

# Get parameter from SSM
ssm-get() {
  aws ssm get-parameter --name "$1" --with-decryption --query Parameter.Value --output text
}

# Quick CloudWatch logs
logs() {
  aws logs tail "$1" --follow --format short
}

Completion

1
2
3
4
5
6
# Enable bash completion
complete -C aws_completer aws

# Or for zsh
autoload bashcompinit && bashcompinit
complete -C aws_completer aws

Debugging

1
2
3
4
5
6
7
8
# Debug API calls
aws ec2 describe-instances --debug

# See what would be sent (dry run)
aws ec2 run-instances --dry-run ...

# Verbose HTTP
aws ec2 describe-instances --debug 2>&1 | grep -A5 "HTTP request"

Quick Reference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Identity
aws sts get-caller-identity

# Regions
aws ec2 describe-regions --query 'Regions[].RegionName' --output text

# List all resource types
aws resourcegroupstaggingapi get-resources --query 'ResourceTagMappingList[].ResourceARN'

# Export credentials (for tools that don't use AWS config)
eval $(aws configure export-credentials --format env)

The AWS CLI replaces hours of console clicking with seconds of scripting. Master JMESPath queries for filtering, use server-side --filters for performance, and build shell functions for your common workflows. The console is for learning; the CLI is for doing.