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.
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
|
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.
📬 Get the Newsletter
Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.