|
#!/usr/bin/env bash |
|
# |
|
# dotenv-mask-output.sh — mask sensitive values from a .env-style file so it |
|
# can be safely pasted, logged, or shared (e.g. in a bug report or CI log) |
|
# without leaking secrets. Keys are never touched, only values — and only |
|
# values whose key looks sensitive get masked at all. |
|
# |
|
# https://github.com/KenyaWestDev/dotenv-mask-output |
|
# |
|
# Usage: |
|
# ./dotenv-mask-output.sh [options] |
|
# |
|
# Run with --help for the full option list. |
|
|
|
set -uo pipefail |
|
shopt -s extglob |
|
|
|
# Capture the external NO_COLOR convention (https://no-color.org) before |
|
# the internal NO_COLOR flag below shadows the name. |
|
_EXTERNAL_NO_COLOR="${NO_COLOR:-}" |
|
|
|
if ((BASH_VERSINFO[0] < 4)); then |
|
printf 'dotenv-mask-output: requires bash 4.0+ (found %s)\n' "$BASH_VERSION" >&2 |
|
exit 3 |
|
fi |
|
|
|
SCRIPT_NAME="dotenv-mask-output.sh" |
|
VERSION="1.0.0" |
|
|
|
# --------------------------------------------------------------------------- |
|
# Defaults |
|
# --------------------------------------------------------------------------- |
|
CANDIDATES=( |
|
".env" |
|
".env.local" |
|
".env.development.local" |
|
".env.development" |
|
".env.dev" |
|
".env.test.local" |
|
".env.test" |
|
".env.staging.local" |
|
".env.staging" |
|
".env.production.local" |
|
".env.production" |
|
".env.prod" |
|
) |
|
|
|
MODE="word" # word | asterisk |
|
MASK_CHAR="*" |
|
MASK_LEN="match" # "match" or a positive integer |
|
SIMPLE=false # word mode: skip type/category detection, always use TAG_WORD |
|
TAG_WORD="redacted" # generic fallback tag |
|
UPPER=false # <TOKEN> instead of <token> |
|
MASK_ALL=false |
|
EXTRA_KEYS="" |
|
EXCLUDE_KEYS="" |
|
WALK_UP=false |
|
FILE="" |
|
OUTPUT="" |
|
IN_PLACE=false |
|
ASSUME_YES=false |
|
LIST_ONLY=false |
|
DRY_RUN=false |
|
QUIET=false |
|
VERBOSE=false |
|
# Disable color automatically when stderr isn't a terminal or NO_COLOR is |
|
# set in the environment. --no-color (parsed below) always wins if passed |
|
# explicitly. |
|
NO_COLOR=false |
|
[[ -t 2 ]] || NO_COLOR=true |
|
[[ -n "$_EXTERNAL_NO_COLOR" ]] && NO_COLOR=true |
|
|
|
# --------------------------------------------------------------------------- |
|
# Small helpers |
|
# --------------------------------------------------------------------------- |
|
c_reset=""; c_dim=""; c_red=""; c_yellow=""; c_cyan="" |
|
init_colors() { |
|
[[ "$NO_COLOR" == true ]] && return |
|
c_reset=$'\033[0m'; c_dim=$'\033[2m'; c_red=$'\033[31m' |
|
c_yellow=$'\033[33m'; c_cyan=$'\033[36m' |
|
} |
|
|
|
log_info() { [[ "$QUIET" == true ]] && return; printf '%s[info]%s %s\n' "$c_cyan" "$c_reset" "$*" >&2; } |
|
log_warn() { [[ "$QUIET" == true ]] && return; printf '%s[warn]%s %s\n' "$c_yellow" "$c_reset" "$*" >&2; } |
|
log_verb() { [[ "$VERBOSE" == true ]] || return; printf '%s[verbose]%s %s\n' "$c_dim" "$c_reset" "$*" >&2; } |
|
log_err() { printf '%s[error]%s %s\n' "$c_red" "$c_reset" "$*" >&2; } |
|
|
|
trim() { |
|
local s="$1" |
|
s="${s#"${s%%[![:space:]]*}"}" |
|
s="${s%"${s##*[![:space:]]}"}" |
|
printf '%s' "$s" |
|
} |
|
|
|
usage() { |
|
cat <<EOF |
|
$SCRIPT_NAME v$VERSION |
|
Mask sensitive values in a .env-style file. Keys are left alone; only |
|
values belonging to sensitive-looking keys are masked. |
|
|
|
USAGE: |
|
$SCRIPT_NAME [options] |
|
|
|
FILE SELECTION: |
|
-f, --file FILE Read FILE instead of auto-detecting one. Use |
|
"-" to read from stdin. |
|
-r, --walk-up When auto-detecting, also search parent |
|
directories (nearest match wins). |
|
-L, --list List candidate env files and which one would |
|
be selected, then exit (no masking). |
|
|
|
OUTPUT: |
|
-o, --output FILE Write masked output to FILE instead of stdout. |
|
-i, --in-place Overwrite the source file with masked output. |
|
Asks for confirmation unless -y is given. |
|
-y, --yes Assume "yes" to any confirmation prompt. |
|
-n, --dry-run Print which keys would be masked (and with |
|
what tag) instead of producing full output. |
|
|
|
MASKING STYLE: |
|
-m, --mode MODE "word" (default) or "asterisk". |
|
-c, --char CHAR Character to repeat in asterisk mode. [*] |
|
--length N|match Asterisk run length: a fixed number, or |
|
"match" to mirror the original value's |
|
length. [match] |
|
-s, --simple In word mode, always use the generic tag |
|
(see --tag-word) instead of detecting a |
|
value's type/category. |
|
-w, --tag-word WORD Generic fallback tag used in word mode when |
|
no more specific category applies, and |
|
always used in --simple mode. [redacted] |
|
-U, --upper Upper-case tag words: <TOKEN> instead of |
|
<token>. |
|
|
|
WHAT GETS MASKED: |
|
-a, --all Mask every value, regardless of key name. |
|
-k, --extra-keys LIST Comma-separated substrings; keys |
|
containing any of them are also treated |
|
as sensitive. |
|
-e, --exclude-keys LIST Comma-separated substrings; keys |
|
containing any of them are never |
|
masked (takes priority over everything |
|
else, including --all). |
|
|
|
MISC: |
|
-q, --quiet Suppress informational messages. |
|
-v, --verbose Print extra diagnostic messages. |
|
--no-color Disable colored messages. |
|
-h, --help Show this help and exit. |
|
-V, --version Show version and exit. |
|
|
|
AUTO-DETECTION ORDER (first existing file wins): |
|
${CANDIDATES[*]} |
|
|
|
EXAMPLES: |
|
$SCRIPT_NAME # auto-detect, word tags, to stdout |
|
$SCRIPT_NAME -m asterisk -c '#' # asterisk-style masking |
|
$SCRIPT_NAME -f .env.production -o out.env |
|
$SCRIPT_NAME -k stripe,slack -e port,name |
|
$SCRIPT_NAME --dry-run -v |
|
EOF |
|
} |
|
|
|
version() { printf '%s v%s\n' "$SCRIPT_NAME" "$VERSION"; } |
|
|
|
# --------------------------------------------------------------------------- |
|
# Argument parsing |
|
# --------------------------------------------------------------------------- |
|
while [[ $# -gt 0 ]]; do |
|
arg="$1" |
|
case "$arg" in |
|
-f|--file) FILE="${2:-}"; shift 2 ;; |
|
--file=*) FILE="${arg#*=}"; shift ;; |
|
-o|--output) OUTPUT="${2:-}"; shift 2 ;; |
|
--output=*) OUTPUT="${arg#*=}"; shift ;; |
|
-i|--in-place) IN_PLACE=true; shift ;; |
|
-y|--yes) ASSUME_YES=true; shift ;; |
|
-m|--mode) MODE="${2:-}"; shift 2 ;; |
|
--mode=*) MODE="${arg#*=}"; shift ;; |
|
-c|--char) MASK_CHAR="${2:-}"; shift 2 ;; |
|
--char=*) MASK_CHAR="${arg#*=}"; shift ;; |
|
--length) MASK_LEN="${2:-}"; shift 2 ;; |
|
--length=*) MASK_LEN="${arg#*=}"; shift ;; |
|
-s|--simple) SIMPLE=true; shift ;; |
|
-w|--tag-word) TAG_WORD="${2:-}"; shift 2 ;; |
|
--tag-word=*) TAG_WORD="${arg#*=}"; shift ;; |
|
-U|--upper) UPPER=true; shift ;; |
|
-a|--all) MASK_ALL=true; shift ;; |
|
-k|--extra-keys) EXTRA_KEYS="${2:-}"; shift 2 ;; |
|
--extra-keys=*) EXTRA_KEYS="${arg#*=}"; shift ;; |
|
-e|--exclude-keys) EXCLUDE_KEYS="${2:-}"; shift 2 ;; |
|
--exclude-keys=*) EXCLUDE_KEYS="${arg#*=}"; shift ;; |
|
-r|--walk-up) WALK_UP=true; shift ;; |
|
-L|--list) LIST_ONLY=true; shift ;; |
|
-n|--dry-run) DRY_RUN=true; shift ;; |
|
-q|--quiet) QUIET=true; shift ;; |
|
-v|--verbose) VERBOSE=true; shift ;; |
|
--no-color) NO_COLOR=true; shift ;; |
|
-h|--help) usage; exit 0 ;; |
|
-V|--version) version; exit 0 ;; |
|
--) shift; break ;; |
|
-*) |
|
log_err "unknown option: $arg" |
|
log_err "run '$SCRIPT_NAME --help' for usage" |
|
exit 2 |
|
;; |
|
*) |
|
log_err "unexpected argument: $arg" |
|
exit 2 |
|
;; |
|
esac |
|
done |
|
|
|
init_colors |
|
|
|
if [[ "$MODE" != "word" && "$MODE" != "asterisk" ]]; then |
|
log_err "invalid --mode '$MODE' (expected 'word' or 'asterisk')" |
|
exit 2 |
|
fi |
|
if [[ -n "$MASK_CHAR" && ${#MASK_CHAR} -ne 1 ]]; then |
|
log_err "--char must be exactly one character" |
|
exit 2 |
|
fi |
|
if [[ "$MASK_LEN" != "match" && ! "$MASK_LEN" =~ ^[1-9][0-9]*$ ]]; then |
|
log_err "--length must be 'match' or a positive integer" |
|
exit 2 |
|
fi |
|
if [[ "$IN_PLACE" == true && -n "$OUTPUT" ]]; then |
|
log_err "--in-place and --output are mutually exclusive" |
|
exit 2 |
|
fi |
|
|
|
# --------------------------------------------------------------------------- |
|
# File discovery |
|
# --------------------------------------------------------------------------- |
|
find_env_file() { |
|
local dir="$PWD" c parent |
|
while true; do |
|
for c in "${CANDIDATES[@]}"; do |
|
if [[ -f "$dir/$c" ]]; then |
|
printf '%s\n' "$dir/$c" |
|
return 0 |
|
fi |
|
done |
|
[[ "$WALK_UP" == true ]] || break |
|
parent=$(dirname "$dir") |
|
[[ "$parent" == "$dir" ]] && break |
|
dir="$parent" |
|
done |
|
return 1 |
|
} |
|
|
|
if [[ "$LIST_ONLY" == true ]]; then |
|
printf 'Search order:\n' |
|
for c in "${CANDIDATES[@]}"; do |
|
if [[ -f "$PWD/$c" ]]; then |
|
printf ' %s%-32s%s FOUND\n' "$c_cyan" "$c" "$c_reset" |
|
else |
|
printf ' %-32s -\n' "$c" |
|
fi |
|
done |
|
if selected=$(find_env_file); then |
|
printf '\nSelected: %s\n' "$selected" |
|
else |
|
printf '\nSelected: (none found)\n' |
|
fi |
|
exit 0 |
|
fi |
|
|
|
if [[ -z "$FILE" ]]; then |
|
if ! FILE=$(find_env_file); then |
|
log_err "no .env-style file found in $PWD (try -f to specify one, or -r to search parent directories)" |
|
exit 1 |
|
fi |
|
log_verb "auto-detected: $FILE" |
|
fi |
|
|
|
if [[ "$FILE" != "-" && ! -f "$FILE" ]]; then |
|
log_err "file not found: $FILE" |
|
exit 1 |
|
fi |
|
|
|
# --------------------------------------------------------------------------- |
|
# Classification: is a key sensitive, and if so what tag describes it? |
|
# --------------------------------------------------------------------------- |
|
key_matches_list() { |
|
local lkey="$1" list="$2" item items |
|
[[ -z "$list" ]] && return 1 |
|
IFS=',' read -ra items <<< "$list" |
|
for item in "${items[@]}"; do |
|
item="$(trim "$item")" |
|
item="${item,,}" |
|
[[ -z "$item" ]] && continue |
|
[[ "$lkey" == *"$item"* ]] && return 0 |
|
done |
|
return 1 |
|
} |
|
|
|
# echoes "<true|false> <tag>" |
|
classify_key() { |
|
local key="$1" lkey tag="" sensitive=false |
|
lkey="${key,,}" |
|
|
|
if key_matches_list "$lkey" "$EXCLUDE_KEYS"; then |
|
printf 'false \n' |
|
return |
|
fi |
|
|
|
[[ "$MASK_ALL" == true ]] && sensitive=true |
|
|
|
case "$lkey" in |
|
*password*|*passwd*|*pwd*|*pass*) tag=password; sensitive=true ;; |
|
*secret*) tag=secret; sensitive=true ;; |
|
*token*) tag=token; sensitive=true ;; |
|
*apikey*|*api_key*|*privatekey*|*private_key*|*publickey*|*public_key*|*key*) tag=key; sensitive=true ;; |
|
*credential*|*cred*) tag=credential; sensitive=true ;; |
|
*jwt*) tag=token; sensitive=true ;; |
|
*auth*) tag=auth; sensitive=true ;; |
|
*session*) tag=session; sensitive=true ;; |
|
*cookie*) tag=cookie; sensitive=true ;; |
|
*cert*) tag=certificate; sensitive=true ;; |
|
*salt*|*hash*) tag=hash; sensitive=true ;; |
|
*license*) tag=license; sensitive=true ;; |
|
*webhook*|*url*|*uri*|*endpoint*) tag=url; sensitive=true ;; |
|
*domain*|*hostname*|*host*) tag=domain; sensitive=true ;; |
|
*email*|*mail*) tag=email; sensitive=true ;; |
|
*phone*|*mobile*|*telnum*) tag=phone; sensitive=true ;; |
|
*dsn*|*connection*|*database*) tag=connection; sensitive=true ;; |
|
*smtp*) tag=secret; sensitive=true ;; |
|
*user*) tag=username; sensitive=true ;; |
|
*expiry*|*expires*) tag=date; sensitive=true ;; |
|
*date*|*time*) tag=date; sensitive=true ;; |
|
*version*) tag=version ;; |
|
*path*|*dir*|*file*) tag=path ;; |
|
*name*) tag=text ;; |
|
*port*) tag=number ;; |
|
esac |
|
|
|
# Short/ambiguous substrings: require an underscore or start/end boundary |
|
# so e.g. "SHIP_DATE" doesn't match "ip" and "VALID" doesn't match "id". |
|
if [[ "$lkey" =~ (^|_)ip(_|$) ]]; then tag=ip; sensitive=true; fi |
|
if [[ -z "$tag" && "$lkey" =~ (^|_)id(_|$) ]]; then tag=id; sensitive=true; fi |
|
if [[ -z "$tag" && "$lkey" =~ (^|_)db(_|$) ]]; then tag=connection; sensitive=true; fi |
|
if [[ "$lkey" =~ (^|_)pin(_|$) ]]; then tag=pin; sensitive=true; fi |
|
if [[ "$lkey" =~ (^|_)otp(_|$) ]]; then tag=otp; sensitive=true; fi |
|
if [[ -z "$tag" && "$lkey" =~ (^|_)access(_|$) ]]; then tag=token; sensitive=true; fi |
|
if [[ -z "$tag" && "$lkey" =~ (^|_)private(_|$) ]]; then tag=key; sensitive=true; fi |
|
|
|
if key_matches_list "$lkey" "$EXTRA_KEYS"; then sensitive=true; fi |
|
|
|
printf '%s %s\n' "$sensitive" "$tag" |
|
} |
|
|
|
detect_type() { |
|
local v="$1" lv |
|
[[ -z "$v" ]] && { printf ''; return; } |
|
if [[ "$v" =~ ^-?[0-9]+$ ]]; then printf 'number'; return; fi |
|
if [[ "$v" =~ ^-?[0-9]+\.[0-9]+$ ]]; then printf 'number'; return; fi |
|
lv="${v,,}" |
|
if [[ "$lv" == "true" || "$lv" == "false" || "$lv" == "yes" || "$lv" == "no" ]]; then |
|
printf 'boolean'; return |
|
fi |
|
if [[ "$v" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then printf 'ip'; return; fi |
|
if [[ "$v" =~ ^[A-Za-z][A-Za-z0-9+.-]*://.+$ ]]; then printf 'domain'; return; fi |
|
if [[ "$v" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then printf 'email'; return; fi |
|
if [[ "$v" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}([Tt][0-9]{2}:[0-9]{2}(:[0-9]{2})?(Z|[+-][0-9]{2}:?[0-9]{2})?)?$ ]]; then |
|
printf 'date'; return |
|
fi |
|
if [[ "$v" =~ ^[0-9]{2}/[0-9]{2}/[0-9]{4}$ ]]; then printf 'date'; return; fi |
|
if [[ "$v" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$ ]]; then |
|
printf 'domain'; return |
|
fi |
|
printf 'text' |
|
} |
|
|
|
# --------------------------------------------------------------------------- |
|
# Masking primitives |
|
# --------------------------------------------------------------------------- |
|
mask_asterisk() { |
|
local value="$1" n out |
|
if [[ -z "$value" ]]; then printf ''; return; fi |
|
if [[ "$MASK_LEN" == "match" ]]; then n=${#value}; else n=$MASK_LEN; fi |
|
((n < 1)) && n=1 |
|
printf -v out '%*s' "$n" '' |
|
out="${out// /$MASK_CHAR}" |
|
printf '%s' "$out" |
|
} |
|
|
|
mask_word() { |
|
local key="$1" value="$2" tag="$3" out proto="" |
|
if [[ "$SIMPLE" == true ]]; then |
|
tag="$TAG_WORD" |
|
elif [[ -z "$tag" ]]; then |
|
tag=$(detect_type "$value") |
|
[[ -z "$tag" ]] && tag="$TAG_WORD" |
|
fi |
|
if [[ "$tag" == "domain" || "$tag" == "url" ]]; then |
|
if [[ "$value" =~ ^([A-Za-z][A-Za-z0-9+.-]*://) ]]; then |
|
proto="${BASH_REMATCH[1]}" |
|
fi |
|
fi |
|
[[ "$UPPER" == true ]] && tag="${tag^^}" |
|
out="${proto}<${tag}>" |
|
printf '%s' "$out" |
|
} |
|
|
|
mask_value() { |
|
local key="$1" value="$2" tag="$3" |
|
[[ -z "$value" ]] && { printf '%s' "$value"; return; } |
|
if [[ "$MODE" == "asterisk" ]]; then |
|
mask_asterisk "$value" |
|
else |
|
mask_word "$key" "$value" "$tag" |
|
fi |
|
} |
|
|
|
# --------------------------------------------------------------------------- |
|
# Line processing |
|
# --------------------------------------------------------------------------- |
|
FULL_RE='^([[:space:]]*)(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_.]*)([[:space:]]*=[[:space:]]*)(.*)$' |
|
DQ_RE='^"((\\.|[^"\\])*)"(.*)$' |
|
SQ_RE="^'([^']*)'(.*)\$" |
|
|
|
declare -A DRY_SEEN=() |
|
|
|
process_line() { |
|
local raw="$1" cr="" |
|
if [[ "$raw" == *$'\r' ]]; then |
|
cr=$'\r' |
|
raw="${raw%$'\r'}" |
|
fi |
|
|
|
if [[ "$raw" =~ ^[[:space:]]*$ ]]; then |
|
[[ "$DRY_RUN" == true ]] && return |
|
printf '%s%s\n' "$raw" "$cr" |
|
return |
|
fi |
|
if [[ "$raw" =~ ^[[:space:]]*# ]]; then |
|
[[ "$DRY_RUN" == true ]] && return |
|
printf '%s%s\n' "$raw" "$cr" |
|
return |
|
fi |
|
if [[ ! "$raw" =~ $FULL_RE ]]; then |
|
[[ "$DRY_RUN" == true ]] && return |
|
printf '%s%s\n' "$raw" "$cr" |
|
return |
|
fi |
|
|
|
local prefix key rest |
|
prefix="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}${BASH_REMATCH[4]}" |
|
key="${BASH_REMATCH[3]}" |
|
rest="${BASH_REMATCH[5]}" |
|
|
|
local decision sensitive tag |
|
decision=$(classify_key "$key") |
|
sensitive="${decision%% *}" |
|
tag="${decision#* }" |
|
|
|
if [[ "$sensitive" != true ]]; then |
|
if [[ "$DRY_RUN" == true ]]; then |
|
if [[ -z "${DRY_SEEN[$key]:-}" ]]; then |
|
DRY_SEEN[$key]=1 |
|
[[ "$VERBOSE" == true ]] && printf '%-40s -> unmasked\n' "$key" |
|
fi |
|
return |
|
fi |
|
printf '%s%s%s\n' "$prefix" "$rest" "$cr" |
|
return |
|
fi |
|
|
|
local quote="" inner="" trailing="" masked |
|
|
|
if [[ "$rest" =~ $DQ_RE ]]; then |
|
quote='"' |
|
inner="${BASH_REMATCH[1]}" |
|
trailing="${BASH_REMATCH[3]}" |
|
elif [[ "$rest" =~ $SQ_RE ]]; then |
|
quote="'" |
|
inner="${BASH_REMATCH[1]}" |
|
trailing="${BASH_REMATCH[2]}" |
|
else |
|
local before_hash comment_part value_part value_trimmed trailing_ws |
|
if [[ "$rest" == *'#'* ]]; then |
|
before_hash="${rest%%#*}" |
|
comment_part="#${rest#*#}" |
|
else |
|
before_hash="$rest" |
|
comment_part="" |
|
fi |
|
value_part="$before_hash" |
|
value_trimmed="${value_part%%+([[:space:]])}" |
|
trailing_ws="${value_part#"$value_trimmed"}" |
|
inner="$value_trimmed" |
|
trailing="${trailing_ws}${comment_part}" |
|
quote="" |
|
fi |
|
|
|
if [[ "$DRY_RUN" == true ]]; then |
|
if [[ -z "${DRY_SEEN[$key]:-}" ]]; then |
|
DRY_SEEN[$key]=1 |
|
if [[ -z "$inner" ]]; then |
|
printf '%-40s -> empty, nothing to mask\n' "$key" |
|
else |
|
local shown="$tag" |
|
[[ -z "$shown" ]] && shown="(type-detected)" |
|
printf '%-40s -> mask as <%s>\n' "$key" "$shown" |
|
fi |
|
fi |
|
return |
|
fi |
|
|
|
if [[ -z "$inner" ]]; then |
|
printf '%s%s%s%s%s%s\n' "$prefix" "$quote" "$inner" "$quote" "$trailing" "$cr" |
|
return |
|
fi |
|
|
|
masked=$(mask_value "$key" "$inner" "$tag") |
|
printf '%s%s%s%s%s%s\n' "$prefix" "$quote" "$masked" "$quote" "$trailing" "$cr" |
|
} |
|
|
|
# --------------------------------------------------------------------------- |
|
# Run |
|
# --------------------------------------------------------------------------- |
|
if [[ "$IN_PLACE" == true && "$ASSUME_YES" != true ]]; then |
|
if [[ -t 0 ]]; then |
|
printf 'Overwrite %s with masked output? [y/N] ' "$FILE" >&2 |
|
read -r reply |
|
[[ "$reply" =~ ^[Yy]$ ]] || { log_info "aborted"; exit 1; } |
|
else |
|
log_err "--in-place requires --yes when not running interactively" |
|
exit 2 |
|
fi |
|
fi |
|
|
|
read_input() { |
|
local src="$1" line |
|
if [[ "$src" == "-" ]]; then |
|
while IFS= read -r line || [[ -n "$line" ]]; do |
|
process_line "$line" |
|
done |
|
else |
|
while IFS= read -r line || [[ -n "$line" ]]; do |
|
process_line "$line" |
|
done < "$src" |
|
fi |
|
} |
|
|
|
if [[ "$DRY_RUN" == true ]]; then |
|
read_input "$FILE" |
|
exit 0 |
|
fi |
|
|
|
if [[ "$IN_PLACE" == true ]]; then |
|
tmp_out=$(mktemp "${TMPDIR:-/tmp}/dotenv-mask-output.XXXXXX") |
|
read_input "$FILE" > "$tmp_out" |
|
mv "$tmp_out" "$FILE" |
|
log_info "masked output written to $FILE" |
|
elif [[ -n "$OUTPUT" ]]; then |
|
read_input "$FILE" > "$OUTPUT" |
|
log_info "masked output written to $OUTPUT" |
|
else |
|
read_input "$FILE" |
|
fi |