- Published on
모던 Bash & 셸 스크립팅 2026 완벽 가이드 - Bash 5.2 · Zsh 5.9 · fish 4 · Nushell · Murex · ShellCheck · Bashly · just 심층 분석
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 프롤로그 — 왜 2026년에 다시 셸을 이야기하는가
- 1장 · Bash 5.2 — Brian Fox가 시작하고 Chet Ramey가 이어가는 표준
- 2장 · Zsh 5.9 — macOS의 기본, Oh My Zsh의 본진
- 3장 · fish 4 — Rust로 다시 태어난 사용자 친화 셸
- 4장 · Nushell — 구조화된 데이터 파이프라인
- 5장 · Murex · xonsh · Elvish — 세 번째 길의 셸들
- 6장 · POSIX 셸 · dash · ksh · tcsh — 레거시와 최소주의
- 7장 · 셸 스크립팅의 strict mode — set -euo pipefail
- 8장 · trap과 mktemp — 청소를 잊지 않기
- 9장 · 인자 파싱 — getopts와 그 너머
- 10장 · 배열과 연관 배열 — bash 4 이후의 핵심
- 11장 · 함수와 local · readonly · process substitution
- 12장 · ShellCheck · shfmt · bashly — 모던 셸 워크플로
- 13장 · fzf · ripgrep · fd · bat · eza — 모던 Unix 도구
- 14장 · 시스템 모니터링 — bottom · btop · htop · glances
- 15장 · jq · yq · dasel · gron — 구조화 데이터 처리
- 16장 · 병렬 실행 — xargs · parallel · rush
- 17장 · gum · charm — 셸에서 예쁜 TUI
- 18장 · just · task · xc · mise — 모던 태스크 러너
- 19장 · 셸 프롬프트 — Starship · Oh My Posh · Powerline
- 20장 · AI 셸 — sgpt · gptme · Warp · Amazon Q
- 21장 · 터미널 에뮬레이터 — Ghostty · Wezterm · Alacritty · Kitty · iTerm2
- 22장 · 한일 운영팀의 셸 — 쿠팡 · LINE · 사이보즈 · 메르카리
- 23장 · 2026~2028년 셸의 미래
- 24장 · 셸 스크립팅 체크리스트 (10가지)
- 25장 · 7가지 실전 안티패턴 카탈로그
- 26장 · 결론 — 셸은 여전히 인프라의 한가운데에 있다
- 참고 자료(References)
프롤로그 — 왜 2026년에 다시 셸을 이야기하는가
1989년 6월. Brian Fox가 GNU 프로젝트의 일환으로 Bourne-Again SHell의 첫 버전을 공개했다. 그로부터 37년이 지난 2026년 5월, 우리는 여전히 매일 셸을 두드린다. AI가 코드를 짜주는 시대에도 cd, ls, grep, git, kubectl, ssh는 손가락의 근육 기억으로 남아 있다.
그러나 풍경은 분명히 변했다.
- Bash 5.2 (2022년 9월 릴리스)가 여전히 GNU 표준이고, 5.3은 2026년 안에 도착 예정이다. Chet Ramey가 1990년대 초부터 관리한다.
- Zsh 5.9가 macOS Catalina 이후 기본 셸이고, Oh My Zsh · Prezto · Antigen 같은 거대한 플러그인 생태계를 거느린다.
- fish 4가 2024년 Rust로 완전히 재작성됐다. 시작 시간, syntax highlight, autosuggestion이 더 빨라졌고 메모리도 줄었다.
- **Nushell 0.105+**가 셸의 정의 자체를 흔든다. 모든 명령이 구조화된 데이터(table)를 주고받고,
ls | where size > 1mb | sort-by modified처럼 SQL스러운 파이프라인을 짠다. - Murex — 타입 시스템을 가진 셸. xonsh — Python으로 짜는 셸. Elvish — 표현력 강한 셸. 모두 자기 자리를 찾았다.
- 그리고 도구들. ShellCheck는 셸 스크립트의 ESLint다. shfmt는 포매터다. bashly는 bash로 풀스택 CLI 앱을 생성한다. gum은 셸에서 예쁜 TUI를 만든다. fzf, ripgrep, fd, eza, dust, bottom, just, mise — 모두 GNU coreutils를 Rust/Go로 다시 쓴 차세대 도구들이다.
- AI도 들어왔다. **shell_gpt(sgpt)**가 자연어로 셸 명령을 만들고, Warp Terminal이 AI 명령 제안을 띄우고, Amazon Q Developer(구 Fig)가 자동완성을 강화한다.
이 글은 위 도구와 패턴을 22개 챕터로 정리한 심층 가이드다. 셸 자체(16장), 스크립팅 베스트 프랙티스(712장), 모던 CLI 도구(1317장), 빌드/태스크 러너(1819장), AI와 터미널(20장), 한일 운영팀 사례(21장), 그리고 미래(22장) 순서다.
셸은 사라지지 않는다. 다만 셸은 더 이상 단순한 입력 줄이 아니다. 우리는 “셸 vs IDE”의 시대에서 “구조화된 셸 + 모던 도구 + AI”의 시대로 넘어왔다.
1장 · Bash 5.2 — Brian Fox가 시작하고 Chet Ramey가 이어가는 표준
Bash는 GNU 프로젝트의 표준 셸이다. 1989년 Brian Fox가 처음 만들었고, 1992년 이후 Chet Ramey(케이스 웨스턴 리저브 대학)가 거의 혼자 유지보수해 왔다. 2026년 5월 현재 안정 버전은 5.2.37(2025년 11월 패치 릴리스)이고, 5.3은 같은 해 말 릴리스가 예고돼 있다.
Bash 5.2의 새 기능 정리
BASH_REMATCH의 정규식 매치가 더 안정적이다.globskipdotsshell option —*매치에서.과..을 빠뜨려 흔한 실수를 막는다.varredir_close— process substitution이 끝날 때 fd를 안전하게 닫는다.${var@k}파라미터 변환 — 키만 뽑아오는 새 패턴.wait -p VAR—wait의 결과 PID를 변수에 넣는다.- 함수 추적이 개선돼
set -T/trap ... DEBUG조합이 더 정확하다.
Bash 5.3 예고
BASH_TRAPSIG— 트랩이 처리한 시그널 번호를 변수로 노출.set -o curses비슷한 모드 검토.- 일부 POSIX 호환성 강화.
왜 아직도 bash인가
세 가지 답이 있다. 1) 편재성(ubiquity) — Linux, macOS, WSL, BusyBox, Docker alpine까지 거의 모든 곳에 있다. 2) CI/CD 표준 — GitHub Actions, GitLab CI, Jenkins의 기본 셸이 bash다. 3) 40년치 자료 — man bash, Stack Overflow, ABS Guide의 누적 자료가 다른 셸을 압도한다.
# Bash 5.2의 새 기능 예시
shopt -s globskipdots # *.txt가 .과 ..을 안 잡게 한다
echo *
# wait -p로 백그라운드 PID 받기
sleep 5 &
wait -p PID $!
echo "지난 백그라운드 PID는 $PID"
# 파라미터 변환의 새 양식
declare -A map=([a]=1 [b]=2)
echo "${map[@]@k}" # 키만 나열
macOS의 함정 — Bash 3.2.57
여기서 한 가지 큰 함정이 있다. macOS의 /bin/bash는 2007년부터 멈춘 Bash 3.2.57이다. Apple이 GPLv3를 거부했기 때문이다. 4.0 이후의 모든 기능(declare -A, mapfile, readarray, ${var,,} 소문자화, ** globstar)이 안 된다. 해결책은 brew install bash로 5.2를 설치하고 #!/usr/bin/env bash를 쓰는 것이다. 절대로 #!/bin/bash를 쓰지 말 것 — macOS에서 3.2가 잡힌다.
2장 · Zsh 5.9 — macOS의 기본, Oh My Zsh의 본진
Zsh는 1990년 Paul Falstad(프린스턴)이 시작했다. 2026년 5월 현재 5.9가 안정 버전이고, 5.10이 곧 도착한다. 2019년 macOS Catalina에서 Apple이 기본 셸을 Bash 3.2에서 Zsh 5.7로 바꾼 이후, 세계 개발자 노트북의 절반 이상이 Zsh를 쓴다(StackOverflow 개발자 서베이 2025).
Zsh의 핵심 차별점
- 전역 별칭(global alias) —
alias -g G='| grep'로cat foo G bar처럼 어디서나 호출. - 확장 globbing —
**/*.go(.)로 일반 파일만 매치,*(om[1,10])으로 최신 10개. - 연관 배열 기본 내장(Bash는 4.0부터).
- 프로세스 substitution + redirection multi-fanout —
cmd > >(tee a) > >(tee b). - prompt 시스템이 풍부 —
PROMPT_SUBST로 동적 prompt 표현. - completion 시스템이 압도적 —
compinit이 거의 모든 명령어의 옵션을 안다.
# Zsh extended globbing
setopt EXTENDED_GLOB
ls **/*.log~*backup* # backup 제외한 모든 .log
ls *(om[1,10]) # 최신 10개 파일
ls *(/^F) # 빈 디렉터리만
Oh My Zsh — 가장 거대한 프레임워크
Robby Russell이 2009년에 시작했다. 2026년 GitHub 별 17만 개. 200+ 테마, 300+ 플러그인. git, kubectl, aws, docker, node, python 플러그인이 자동완성과 alias를 한 줄로 활성화한다.
# ~/.zshrc 예시
plugins=(git kubectl aws docker fzf z)
ZSH_THEME="agnoster"
source $ZSH/oh-my-zsh.sh
Prezto / Zinit / Antidote — 더 빠른 대안
Oh My Zsh는 풍부하지만 시작이 느려질 수 있다. 더 빠른 대안:
- Prezto — Sorin Ionescu, 2012. 모듈식, 가볍다.
- Zinit — turbo mode로 비동기 로딩, 시작 시간 50ms 수준.
- Antidote — 후속 세대, plain-text plugin file.
# Zinit turbo mode 예시 — 비동기로 로딩해 prompt 표시를 막지 않는다
zinit wait lucid for \
OMZP::git \
OMZP::kubectl \
zsh-users/zsh-autosuggestions \
zdharma-continuum/fast-syntax-highlighting
Zsh의 단점
POSIX 100% 호환은 아니다. 일부 스크립트가 bash와 다르게 동작한다. 그래서 #!/usr/bin/env zsh로 명시하거나, 공통 스크립트는 bash로 짜고 인터랙티브만 zsh로 쓰는 분리가 일반적이다.
3장 · fish 4 — Rust로 다시 태어난 사용자 친화 셸
fish는 “friendly interactive shell”의 약자다. 2005년 Axel Liljencrantz가 시작했고, 2024년 4.0에서 C++에서 Rust로 전면 재작성됐다. 2026년 5월 안정 버전 4.1이고, 시작 시간이 30~50ms 수준이다.
fish의 시그니처 — 즉시 작동하는 기본값
- autosuggestion — 히스토리 기반 자동 제안이 항상 켜져 있다. 회색 글씨로 떴다가 →로 확정.
- syntax highlighting — 명령 입력 중에 빨강(틀린 명령)·녹색(정상)·노랑(인용) 색깔이 즉시.
- abbreviations — alias보다 강력. 입력 시점에 확장돼 히스토리에 진짜 명령이 남는다.
- 풍부한 옵션 자동완성 —
man페이지를 파싱해 옵션을 안다. - Web 기반 설정 —
fish_config로 브라우저에서 테마 고른다.
# fish 문법은 bash와 다르다. 변수, 함수, if 모두 더 명확하다.
set -gx PATH $HOME/bin $PATH
function gco --description "git checkout"
git checkout $argv
end
# 추상화(abbreviation): 입력 시점에 확장된다
abbr -a gc git commit
abbr -a gst git status
fish의 단점 — POSIX 비호환
fish는 POSIX 호환이 아니다. [ ], &&, || 등 일부 문법이 다르다. 그래서 .zshrc, .bashrc를 그대로 가져올 수 없다. 스크립트 공유 측면에서는 bash로 짜고 fish는 인터랙티브용으로 쓰는 게 일반적이다.
4.0의 Rust 재작성이 가져온 것
- 시작 시간이 거의 절반으로 줄었다.
- 메모리 사용량 감소.
- C++의 메모리 안전 문제 해결.
- 외부 기여자 진입 장벽이 낮아졌다.
4장 · Nushell — 구조화된 데이터 파이프라인
Nushell은 2019년 Jonathan Turner와 Yehuda Katz가 시작했다. Rust로 작성됐고, 2026년 5월 현재 안정 버전은 0.105다. **“모든 명령이 데이터(table)를 주고받는 셸”**이라는 점에서 PowerShell의 영감을 받았지만, Unix 철학과 결합돼 더 가볍고 빠르다.
시그니처 — 표 기반 파이프
# 디렉터리 목록을 표로 받고, 크기 필터, 정렬, 5개만
ls | where size > 1mb | sort-by modified | first 5
# JSON을 자동으로 파싱해 다룬다
http get https://api.github.com/repos/nushell/nushell | get stargazers_count
# CSV → JSON 변환이 한 줄
open data.csv | to json | save data.json
왜 중요한가
bash 세계에서는 awk, cut, jq, xargs로 텍스트 파이프를 짠다. 한 줄짜리 파싱 코드가 늘면 가독성이 떨어진다. nushell은 표 자체를 1급 객체로 취급하므로, where, sort-by, group-by, select, each처럼 SQL스러운 표현으로 풀어낸다.
한계
- 셸 스크립트의 호환성 문제. bash 스크립트는 그대로 못 돌린다.
- 외부 명령은 텍스트를 주고받으므로, 데이터화하려면
from json/from csv변환이 필요하다. - 0.x 버전대라 매 릴리스마다 문법이 바뀐다 — production 스크립팅보다 인터랙티브 분석/탐색용으로 쓰는 게 안전하다.
누구에게 맞는가
- 데이터 엔지니어 — 로컬에서 작은 데이터를 다루는 셸 환경으로 훌륭하다.
- 클라우드 운영자 —
kubectl get pods -o json | from json | where ...처럼 JSON을 직접 다룬다.
5장 · Murex · xonsh · Elvish — 세 번째 길의 셸들
Murex — 타입 시스템을 가진 셸
영국 개발자 Andrew Stewart가 만든 셸. 2026년 5월 6.x 버전대. 변수가 타입을 가지며, JSON·YAML·TOML을 1급으로 다룬다. nushell보다는 bash 호환에 가까운 절충안이다.
# 변수 타입 선언
set: int counter = 0
set: string name = "world"
# JSON 처리가 1급
open my.json -> [name age] -> format json
xonsh — Python으로 짜는 셸
Anthony Scopatz가 2015년에 시작했다. Python과 셸을 한 파일에 섞어 쓴다. 데이터 사이언티스트가 사랑하는 셸이다.
# .xonshrc — Python과 셸이 섞여 있다
import os
echo @(os.getcwd())
# 셸 변수가 Python 리스트
ls = $(ls -la).split('\n')
for line in ls:
print(line)
Elvish — 표현력 강한 셸
Qi Xiao(中国)가 2014년 시작. Go로 작성됐다. 람다, 클로저, 1급 함수가 셸에 있다. nushell보다 일찍 “구조화된 셸”을 시도한 프로젝트다.
fn greet [name]{
echo "Hello, "$name
}
greet world
셋 다 공통점은
POSIX 호환을 버린 대신 표현력을 얻었다는 점이다. production 서버에서 init script로 쓰기는 위험하지만, 개인 노트북의 인터랙티브 셸이나 데이터 작업용으로는 매력적이다.
6장 · POSIX 셸 · dash · ksh · tcsh — 레거시와 최소주의
POSIX shell은 IEEE 1003.1 표준이 정한 셸이다. bash 4 이후의 화려한 기능([[ ]], (()), declare -A, <<<)을 모두 못 쓴다. 대신 거의 모든 Unix에서 동작한다.
dash(Debian Almquist SHell)는 Debian이 /bin/sh로 채택한 최소 POSIX 셸이다. Ubuntu, Debian의 /bin/sh는 dash다. bash보다 시작이 4~10배 빠르고, init script가 빠르다.
# POSIX 호환 스크립트의 모범
#!/bin/sh
set -eu
# [[ ]] 못 씀, [ ] 만 가능
if [ "$1" = "build" ]; then
echo "building..."
fi
# 배열도 없음 — 위치 매개변수로 대신
set -- a b c
for x in "$@"; do echo "$x"; done
ksh(Korn Shell)는 1983년 Bell Labs의 David Korn이 만들었다. 한때 Solaris/AIX의 표준이었다. ksh93이 마지막 주요 버전이고, 2020년에 ksh93u+m 포크가 나와 지금도 일부 유지된다. AIX/Solaris 레거시 시스템에서만 본다.
tcsh는 C shell의 후예다. BSD의 일부에서 디폴트로 쓰였다. 1990년대 학교 Unix에서 많이 봤다. 2026년에는 거의 안 쓴다 — FreeBSD가 root의 디폴트로 유지하는 정도다.
언제 무엇을 쓰는가
- production 서버 init/cron 스크립트 → POSIX
/bin/sh(dash/ash). 빠르고 확실하다. - 개발자 노트북 일반 스크립트 → bash 5.2. 풍부한 기능, 광범위한 자료.
- 인터랙티브 일상 셸 → zsh 또는 fish.
- AIX/Solaris 레거시 → ksh.
7장 · 셸 스크립팅의 strict mode — set -euo pipefail
#!/usr/bin/env bash로 시작하는 모든 스크립트의 둘째 줄은 거의 항상 set -euo pipefail이어야 한다.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
각 옵션이 막아주는 함정
set -e(errexit) — 명령이 0이 아닌 상태로 끝나면 즉시 종료.if/while/||안에서는 적용 안 됨(이건 의도된 예외다).set -u(nounset) — 정의 안 된 변수를 쓰면 에러. typo에 의한 silent failure를 막는다.set -o pipefail— 파이프 중간 명령이 실패하면 전체 실패. 기본값에서는 마지막 명령의 종료 코드만 본다.IFS=$'\n\t'— 단어 분리를 줄바꿈/탭으로 한정해 공백 포함 파일명에 안전.
set -e의 함정
set -e는 절대적이지 않다. cmd | other, cmd || true, if cmd; then ... 같은 경우에는 작동 안 한다. 그래서 pipefail까지 묶어야 의미가 있다. 또 set -e는 트랩이 잡힌 함수 안의 동작에서 가끔 헷갈리므로, 트랩과 함께 쓸 때는 명시적 if 검사를 선호하는 게 안전하다.
비교 — 잘 작성된 스크립트의 첫 줄들
#!/usr/bin/env bash
# vim: set ft=bash ts=2 sw=2 :
set -Eeuo pipefail # -E는 ERR 트랩을 함수 안에서도 동작시킨다
IFS=$'\n\t'
# 디버그 모드: BASH_X=1 ./run.sh
if [[ "${BASH_X:-0}" == 1 ]]; then
set -x
fi
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
8장 · trap과 mktemp — 청소를 잊지 않기
스크립트가 임시 파일을 만들면, 반드시 청소해야 한다. 단순한 rm은 실패 경로에서 누락된다. trap을 써야 한다.
TMP=$(mktemp -d -t myapp.XXXXXX)
trap 'rm -rf "$TMP"' EXIT
# 더 복잡한 경우: 시그널별로 분리
cleanup() {
local exit_code=$?
rm -rf "$TMP"
if [[ $exit_code -ne 0 ]]; then
echo "스크립트가 비정상 종료됨(code=$exit_code)" >&2
fi
exit $exit_code
}
trap cleanup EXIT
trap 'echo "INT 받음"; exit 130' INT
trap 'echo "TERM 받음"; exit 143' TERM
mktemp의 베스트 프랙티스
- 항상
-d로 디렉터리를 만들고 그 안에 파일들을 둔다 — 청소가 한 줄. -t prefix.XXXXXX로 prefix를 주면 디버깅이 편하다.- macOS의
mktemp는 GNU와 옵션이 다르다. 크로스 플랫폼은mktemp -d "tmp.XXXXXX"처럼 짧은 형식을 쓴다.
왜 /tmp/myscript를 쓰면 안 되는가
세 가지다. 1) TOCTOU 공격 — 누군가 미리 같은 이름을 만들어두면 권한 우회. 2) 충돌 — 동시에 두 인스턴스가 돌면 충돌. 3) 청소 불가 — 다른 인스턴스의 파일을 삭제하면 위험.
9장 · 인자 파싱 — getopts와 그 너머
기본은 getopts
bash 빌트인이라 외부 의존이 없다. 단일 문자 옵션만 지원하지만, 99% 케이스에 충분하다.
usage() {
cat <<'USAGE'
사용법: deploy.sh [-e env] [-n name] [-d]
-e env 배포 환경 (dev/staging/prod, 필수)
-n name 서비스 이름 (기본: $(basename $PWD))
-d dry-run 모드
-h 이 도움말
USAGE
}
DRY_RUN=0
NAME="$(basename "$PWD")"
while getopts ":e:n:dh" opt; do
case $opt in
e) ENV="$OPTARG" ;;
n) NAME="$OPTARG" ;;
d) DRY_RUN=1 ;;
h) usage; exit 0 ;;
\?) echo "알 수 없는 옵션: -$OPTARG" >&2; usage; exit 2 ;;
:) echo "옵션 -$OPTARG 에는 인자가 필요" >&2; usage; exit 2 ;;
esac
done
shift $((OPTIND - 1))
: "${ENV:?ENV is required (-e)}"
긴 옵션이 필요하면 — 직접 파싱
# --help, --env=dev 같은 긴 옵션을 직접 처리
while [[ $# -gt 0 ]]; do
case $1 in
--env=*) ENV="${1#*=}"; shift ;;
--env) ENV="$2"; shift 2 ;;
--help|-h) usage; exit 0 ;;
--) shift; break ;;
-*) echo "알 수 없는 옵션: $1" >&2; exit 2 ;;
*) ARGS+=("$1"); shift ;;
esac
done
대안 — bashly / docopt.sh
복잡한 CLI는 bashly(아래 12장)로 코드를 생성하거나, docopt.sh로 도움말에서 파서를 자동 생성한다.
10장 · 배열과 연관 배열 — bash 4 이후의 핵심
일반 배열
arr=("a" "b" "c")
echo "${arr[0]}" # a
echo "${arr[@]}" # 모든 원소
echo "${#arr[@]}" # 원소 개수
arr+=("d") # 추가
unset 'arr[1]' # 인덱스 1 제거 (인덱스가 sparse가 됨에 유의)
연관 배열(associative array) — bash 4.0+, zsh 표준, macOS bash 3.2에서는 안 됨
declare -A users
users[alice]=admin
users[bob]=guest
users[carol]=admin
for u in "${!users[@]}"; do
echo "$u: ${users[$u]}"
done
# 키 존재 여부
[[ -v users[alice] ]] && echo "alice 있음"
배열을 함수로 넘기기 — bash의 영원한 함정
# 잘못된 방식 — 배열이 평탄화돼 한 인자가 됨
print_arr() { for x in "$@"; do echo "$x"; done; }
arr=(a "b c" d)
print_arr "${arr[@]}" # 정답 — 따옴표 + @ 확장으로 원소별 전달
# 더 안전 — nameref(bash 4.3+)
print_by_ref() {
local -n ref=$1
for x in "${ref[@]}"; do echo "$x"; done
}
print_by_ref arr
11장 · 함수와 local · readonly · process substitution
함수의 모범
# 함수는 항상 local 변수를 쓴다. 안 그러면 전역 오염.
greet() {
local name=$1
local greeting=${2:-"Hello"}
echo "$greeting, $name!"
}
# 반환은 stdout으로, exit code는 0/non-zero로
add() {
local a=$1 b=$2
echo $((a + b))
}
sum=$(add 3 4)
readonly로 상수 정의
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TIMEOUT_SECONDS=30
readonly -a ENVS=(dev staging prod)
process substitution — <() >()
파이프로 풀기 힘든 경우의 트릭. 임시 파일을 만들지 않고 fd를 직접 넘긴다.
# 두 명령의 diff
diff <(sort file1) <(sort file2)
# 한 출력을 두 곳에 동시에 (tee)
program | tee >(grep ERROR > err.log) >(grep WARN > warn.log) > full.log
# 입력을 fd로 받는 명령에 그대로 흘리기
psql -f <(echo "SELECT * FROM users WHERE id=$ID")
12장 · ShellCheck · shfmt · bashly — 모던 셸 워크플로
ShellCheck — 셸 스크립트의 ESLint
Vidar Holen(노르웨이)이 2012년에 시작했다. Haskell로 작성됐다. 2026년 5월 현재 0.10대다. 모든 PR에 ShellCheck를 돌리는 것이 표준 관행이다.
# CI에서
shellcheck script.sh
# 자주 잡는 문제들
# SC2086: $var를 quote 안 함 — 단어 분리 위험
# SC2046: $(...) 결과를 quote 안 함
# SC2155: declare/local에서 명령 substitution 결합 시 exit code 손실
# SC2034: 변수가 선언만 되고 안 쓰임
shfmt — 셸 포매터
Daniel Martí(mvdan)가 만든 Go 도구. shellcheck와 같은 위치에 있다. gofmt 스타일로 셸을 일관되게 들여쓰기/정렬한다.
# 들여쓰기 2칸으로, 케이스 패턴 들여쓰고, 표준 입력 처리
shfmt -i 2 -ci -sr -d script.sh
# pre-commit hook 예시
shfmt -l -w . # 모든 .sh 파일 자동 포맷
bashly — bash로 만드는 풀스택 CLI
Danny Ben Shitrit이 만들었다. YAML로 명령 구조를 정의하면 bash 스크립트 풀스택 CLI를 생성한다. argument parsing, help, version, completion까지.
# src/bashly.yml
name: mycli
help: 내 CLI 도구
version: 1.0.0
commands:
- name: deploy
help: 환경에 배포
args:
- name: env
required: true
allowed: [dev, staging, prod]
flags:
- long: --dry-run
short: -d
help: dry-run 모드
bashly generate
./mycli deploy prod --dry-run
13장 · fzf · ripgrep · fd · bat · eza — 모던 Unix 도구
fzf — 퍼지 파인더
Junegunn Choi(한국, 카카오)가 2013년 시작. Go로 작성. 2026년 5월 0.55대. 한국 개발자가 만든 가장 영향력 있는 오픈소스 중 하나다.
# fzf 기본
ls | fzf
history | fzf
git log --oneline | fzf | awk '{print $1}' | xargs git show
# 키 바인딩 (Ctrl+R로 히스토리, Ctrl+T로 파일, Alt+C로 디렉터리)
source /usr/share/fzf/key-bindings.bash
ripgrep(rg) — 빠른 grep
Andrew Gallant(BurntSushi)가 만들었다. Rust로 작성. grep보다 5~10배 빠르고, .gitignore를 기본으로 존중한다.
# 기본 사용
rg "TODO" --type rust
rg -i "error" -C 2 # 대소문자 무시, 2줄 컨텍스트
rg --files-with-matches "Apache"
fd — find 대체
David Peter(sharkdp)가 만든 Rust 도구. find보다 직관적이고 빠르다.
fd "\.rs$" # 모든 Rust 파일
fd -e py -t f # Python 파일만
fd -H "node_modules" # hidden 포함
bat — cat에 syntax
sharkdp의 또 다른 작품. cat에 syntax highlighting과 git diff 표시.
bat README.md
bat -p src/main.rs # plain 모드, syntax만
eza — exa의 후속, ls 대체
ogham이 만든 exa의 fork. 2024년 이후 활발히 유지된다.
eza -l --git --icons
eza --tree --level 2
왜 이걸 다 쓰는가
GNU coreutils는 50년 된 인터페이스다. 옵션이 직관적이지 않고, 출력이 색깔 없으며, .gitignore를 모른다. 위 도구들은 2010년대 이후의 합리적 기본값을 제공한다 — 컬러 출력, 인덱싱, ignore 파일 인식, JSON 출력.
14장 · 시스템 모니터링 — bottom · btop · htop · glances
htop — 1990년대 후반 Hisham Muhammad가 만든 클래식. 컬러 + 마우스 + tree 뷰. 현재까지 가장 널리 깔린다.
btop — Aristocratos가 만든 C++ 후속. 더 화려한 시각화, GPU/디스크/네트워크 그래프, mouse navigation.
bottom(btm) — ClementTsang의 Rust 구현. cross-platform, JSON 설정.
glances — Nicolas Hennion의 Python. 웹 API/Prometheus exporter가 있어 모니터링 통합이 쉽다.
비교
| 도구 | 언어 | 특징 | 시스템 부하 |
|---|---|---|---|
| htop | C | 클래식, 안정 | 최저 |
| btop | C++ | 화려, GPU | 중간 |
| bottom | Rust | cross-platform, 설정 풍부 | 중간 |
| glances | Python | 웹 API, exporter | 약간 높음 |
# bottom 설정 예시 (~/.config/bottom/bottom.toml)
[flags]
hide_avg_cpu = false
mem_as_value = true
network_use_binary_prefix = true
15장 · jq · yq · dasel · gron — 구조화 데이터 처리
jq — JSON의 표준
Stephen Dolan이 2012년 시작. 함수형 JSON 쿼리 언어. 모든 곳에 있다.
curl -s api.github.com/users/torvalds | jq '.public_repos'
kubectl get pods -o json | jq '.items[] | .metadata.name'
cat data.json | jq -r '.users[] | select(.age > 30) | .name'
yq — YAML jq
Mike Farah(mikefarah/yq)의 Go 구현이 표준이다. jq 문법을 거의 그대로 쓴다.
yq '.spec.replicas = 3' deployment.yaml
yq '.services | keys' docker-compose.yml
dasel — 크로스 포맷
JSON, YAML, TOML, XML, CSV를 같은 쿼리로 다룬다.
dasel -f config.yaml '.database.host'
dasel put -f config.json '.version' -v '2.0'
gron — JSON을 grep 가능하게
Tom Hudson(TomNomNom)이 만들었다. JSON을 한 줄짜리 키=값 형식으로 평탄화한다.
gron https://api.github.com/users/torvalds | grep public_repos
# json.public_repos = 7;
각 도구는 셸 파이프 안에 자연스럽게 들어간다. 이게 핵심이다 — 셸 스크립트가 JSON/YAML을 다루는 능력이 2020년대에 비약적으로 좋아졌다.
16장 · 병렬 실행 — xargs · parallel · rush
xargs -P — 가장 단순한 병렬
# 8개 동시에 압축
ls *.log | xargs -n 1 -P 8 gzip
# null-terminated 입력 (안전한 파일명 처리)
find . -name '*.log' -print0 | xargs -0 -n 1 -P 8 gzip
GNU parallel — Ole Tange가 만든 더 풍부한 병렬 실행기
parallel -j 8 gzip ::: *.log
parallel --bar 'convert {} {.}.png' ::: *.jpg
parallel --eta wget {} ::: $(cat urls.txt)
GNU parallel은 인용/이스케이프가 까다롭지만, ETA, 진행 바, retry, 원격 SSH 분산 실행까지 가능하다.
rush — shenwei356(중국)의 Go 구현, 인용 처리가 더 직관적
rush 'echo Hello, {}' ::: a b c
rush -j 8 'gzip {}' ::: $(ls *.log)
언제 무엇을 쓰는가
- 단순 병렬 →
xargs -P. 빠르고 어디나 있다. - 진행 바·재시도가 필요 →
parallel. - 인용 문제를 피하고 싶다 →
rush.
17장 · gum · charm — 셸에서 예쁜 TUI
Charm.sh는 Toby Padilla와 Christian Rocha가 운영하는 Go 라이브러리/도구 컬렉션이다. 셸 스크립트가 예쁜 인터랙티브 UI를 가질 수 있게 한다.
# 사용자 입력 (텍스트, 다중 선택, 확인)
NAME=$(gum input --placeholder "이름?")
ENV=$(gum choose dev staging prod)
gum confirm "정말로 배포할까요?" && deploy.sh "$ENV"
# 스피너
gum spin --spinner dot --title "빌드 중..." -- ./build.sh
# 컬러풀한 출력
gum style --foreground 212 --bold "성공!"
# 마크다운 렌더
gum format -- "# 제목" "**굵게**"
왜 중요한가
bash 스크립트의 인터랙티브 UI는 보통 read로 끝난다. gum은 그것을 “현대적”으로 끌어올린다 — 색상, spinner, multi-select, file picker까지. CI/CD의 결과를 사람이 검토하는 인터랙티브 도구, 데모, 셋업 위저드에 잘 맞는다.
Charm의 다른 도구
- glow — 터미널에서 마크다운 렌더링.
- vhs — 터미널 GIF 녹화(스크립트로 정의).
- bubble tea — Go TUI 프레임워크. lazygit, k9s가 비슷한 카테고리.
18장 · just · task · xc · mise — 모던 태스크 러너
just — Casey Rodarmor의 모던 Makefile
2026년 5월 1.40대. Rust로 작성. Makefile의 좋은 점만 가져오고 함정을 다 뺐다.
# justfile
default: build test
build:
cargo build --release
test:
cargo test
deploy env="dev":
@echo "Deploying to {{env}}"
./scripts/deploy.sh {{env}}
# bash 직접 호출
notes:
#!/usr/bin/env bash
set -euo pipefail
grep -r "TODO" src/
Makefile과 비교한 just의 장점
- 탭 vs 스페이스 문제 없음.
- 변수 확장 일관성 있음(Make의
$$vs$함정 없음). - 의존성이 더 명확하다(
recipe-name: dep1 dep2). .PHONY같은 의식 없이도 기본이 “명령 실행”.
task(taskfile.dev) — YAML 기반
# Taskfile.yml
version: '3'
tasks:
build:
cmds:
- go build ./...
test:
deps: [build]
cmds:
- go test ./...
xc — 마크다운으로 짜는 태스크 러너
Joe Brockwill의 작업. README.md 안에 코드 블록으로 태스크를 정의한다.
## build
```bash
cargo build --release
```
## test
Requires: build
```bash
cargo test
```
mise(구 rtx) — 버전 매니저 + 태스크 러너
Jordan Pittman의 Rust 도구. asdf의 후속으로 시작했지만, 태스크 러너 기능까지 들어왔다.
# .mise.toml
[tools]
node = "20"
python = "3.12"
[tasks.build]
run = "npm run build"
선택 기준
- 단순한 명령 모음 → just. 가장 가볍고 직관적.
- 복잡한 의존 그래프 → task(taskfile).
- 버전 관리도 같이 → mise.
- 자기-문서화가 중요 → xc.
19장 · 셸 프롬프트 — Starship · Oh My Posh · Powerline
Starship — 보편적, 빠른 Rust 프롬프트
2019년 Matan Kushner와 Tim Sosa가 시작. bash, zsh, fish, PowerShell, Nushell, ion, elvish, tcsh, xonsh, cmd 모두 지원. TOML 한 파일로 설정.
# ~/.config/starship.toml
add_newline = false
format = "$directory$git_branch$git_status$character"
[directory]
truncation_length = 3
truncate_to_repo = true
[git_branch]
symbol = " "
[character]
success_symbol = "[➜](bold green)"
error_symbol = "[➜](bold red)"
# bash
eval "$(starship init bash)"
# zsh
eval "$(starship init zsh)"
# fish
starship init fish | source
Oh My Posh — 크로스 셸, Windows 친화
Jan De Dobbeleer의 작업. 본래 PowerShell용이었는데 모든 셸로 확장. Windows 사용자가 많이 쓴다. 테마가 100+ 개로 풍부하다.
Powerline — 레거시
Python으로 작성. 첫 “예쁜 프롬프트” 세대를 만들었다. 2026년에는 거의 사용 안 됨 — Starship이 거의 모든 자리를 이어받았다.
왜 Starship이 표준이 되었나
- 셸에 독립적 — 한 설정 파일로 어디서나 같은 프롬프트. 2) 빠르다 — Rust로 작성, 백그라운드 캐시. 3) 정보가 적절 — git 브랜치, 언어 버전, kubernetes 컨텍스트, AWS 프로파일을 자동 인식.
20장 · AI 셸 — sgpt · gptme · Warp · Amazon Q
shell_gpt(sgpt) — TheR1D
Python으로 작성. OpenAI API를 셸에서 호출.
sgpt "find all .log files modified today"
# 출력: find . -name "*.log" -mtime -1
sgpt --shell "compress all png in this dir"
# (Enter로 실행, e로 편집)
sgpt --code "fibonacci in rust"
gptme — Bjorn Holm
비슷한 컨셉이지만 다중 턴 대화와 도구 호출(파일 읽기/쓰기/실행)을 지원한다. 로컬 LLM에도 연결 가능.
Warp Terminal — AI 우선 터미널
2022년부터 Zach Lloyd가 운영. 2024년 Linux 지원, 2025년 stable. AI command suggestion, AI explain command, notebook 기능. Free 플랜과 Pro 플랜이 있다.
# Warp의 # 명령
# how do I rebase from main?
→ Warp가 git rebase 명령을 제안하고 사용자가 Enter로 실행
Amazon Q Developer(구 Fig)
Fig가 2024년 Amazon에 인수돼 Amazon Q Developer CLI로 통합됐다. 자동완성이 강력해졌고, 자연어로 명령 생성이 가능하다.
Aiken · Codex CLI
Anthropic Claude Code, GitHub Copilot CLI, OpenAI Codex CLI 같은 도구들이 셸 명령을 직접 실행할 수 있는 “에이전트”로 자리잡았다. 이는 셸이 사람뿐만 아니라 에이전트의 인터페이스가 되었음을 의미한다.
셸 + AI의 두 모델
- suggest mode — AI가 명령을 제안하고 사람이 확인 후 실행 (sgpt, Warp).
- agent mode — AI가 직접 셸을 실행하며 결과를 보고 다음 결정 (Claude Code, Codex CLI).
agent mode는 위험하므로 컨테이너/샌드박스 안에서 돌리는 게 안전하다.
21장 · 터미널 에뮬레이터 — Ghostty · Wezterm · Alacritty · Kitty · iTerm2
Ghostty — Mitchell Hashimoto의 새 작품
HashiCorp 창업자(Vagrant, Packer, Terraform)인 Mitchell Hashimoto가 2024년 발표. 2025년 1.0 stable, 2026년 5월 1.2대. Zig로 작성, GPU 가속, macOS native + Linux. 시그니처는 “기본 설정이 매우 좋다”와 “native macOS 통합”.
# ~/.config/ghostty/config
font-family = "JetBrains Mono"
font-size = 14
theme = "Catppuccin Mocha"
window-padding-x = 8
window-padding-y = 8
Wezterm — Wez Furlong
Rust로 작성. Lua 설정. tab/pane, ligature 지원, ssh client 내장.
local wezterm = require 'wezterm'
return {
font = wezterm.font 'JetBrains Mono',
font_size = 14,
color_scheme = 'Dracula',
hide_tab_bar_if_only_one_tab = true,
}
Alacritty — Joe Wilm
Rust로 작성. 가장 빠른 터미널 중 하나. TOML 설정. tab/pane 없음(tmux와 같이 씀).
Kitty — Kovid Goyal
Python + GPU. 강력한 image protocol(직접 PNG 표시).
iTerm2 — George Nachman
macOS 전용 클래식. profile, hotkey, split pane, search, broadcast 등 풍부한 기능. 2026년에도 여전히 macOS 표준 중 하나다.
Hyper · Tabby
Electron 기반. 사용자가 적다 — 무겁고 GPU 가속이 약하다.
선택 기준
- macOS 가벼움 → Ghostty 또는 iTerm2.
- 크로스 플랫폼 가벼움 → Alacritty(+tmux) 또는 Kitty.
- 풍부한 설정 → Wezterm.
- AI 통합 → Warp.
22장 · 한일 운영팀의 셸 — 쿠팡 · LINE · 사이보즈 · 메르카리
한국 — 쿠팡 셸 운영의 특징
쿠팡의 인프라 운영팀은 대규모 트래픽을 다루며, 2020년대 초반부터 bash + Ansible + Terraform의 조합을 표준으로 썼다. 셸 스크립트의 베스트 프랙티스(strict mode, shellcheck CI gate)가 사내 PR 룰로 정착됐다고 한국 컨퍼런스 발표에서 공개됐다(if-kakao, AWS Summit Seoul).
한국 — 카카오 fzf의 기원
13장에서 본 fzf는 카카오 출신 개발자 Junegunn Choi의 작품이다. 그는 카카오 시절 사내에서 fzf를 처음 만들었고, 이후 GitHub에 공개해 글로벌 표준이 되었다. 한국 개발자가 만든 가장 영향력 있는 오픈소스 중 하나로 꼽힌다.
일본 — LINE의 셸 운영
LINE은 도쿄 본사에서 수많은 마이크로서비스를 운영한다. 2010년대 후반부터 사내 셸 스크립트 규약을 공개한 LINE Engineering 블로그 글이 일본/한국 양쪽에서 자주 인용된다 — strict mode, mktemp, trap, shellcheck를 의무화한 것이 특징이다.
일본 — 사이보즈(Cybozu)의 운영 도구
사이보즈의 Kintone 팀은 오랜 기간 bash + Ruby + Go 조합으로 운영 도구를 짰다. kintone-cli와 같은 사내 도구가 bash로 짜인 사례가 Cybozu Developer Network에 공개돼 있다.
일본 — 메르카리의 SRE 셸 문화
메르카리는 GCP 기반 마이크로서비스에서 셸 스크립트의 사용을 최소화하고 Go CLI로 대체하는 정책을 SRE 컨퍼런스(SRE NEXT, SREcon)에서 공개했다. 그러나 부팅/디버깅/긴급 운영에는 여전히 bash가 표준이라는 입장이다.
공통 패턴
세 회사 모두 공통적으로 1) set -euo pipefail을 의무화, 2) ShellCheck를 CI gate로, 3) 1000줄을 넘는 스크립트는 Go/Python으로 재작성, 4) #!/usr/bin/env bash를 표준으로 하는 정책을 갖고 있다.
23장 · 2026~2028년 셸의 미래
전망 1 — Bash 5.3 → 6.0의 길
5.3은 2026년 안에 도착할 것이고, 6.0은 2027~2028년쯤 예상된다. 큰 변화는 없을 것이다 — Bash의 철학은 “호환성을 깨지 않는다”다. 다만 internal cleanup, performance, error messages 개선이 계속된다.
전망 2 — fish 4의 Rust 효과
2024년 Rust 재작성 이후 시작 시간/메모리가 크게 줄었다. 2026~2028년에는 외부 기여자가 늘고, 새 기능(structured data 지원 등)이 빨라질 것이다.
전망 3 — Nushell의 진입
production 스크립트로의 진입은 아직 어렵지만, 데이터 엔지니어와 클라우드 운영자의 인터랙티브 셸로 자리를 굳히고 있다. 1.0이 도착하면(2027 예상) 더 빨리 퍼질 것이다.
전망 4 — AI 통합의 표준화
agent mode 셸 사용이 보편화될 것이다. 그에 따라 샌드박스, 권한 제한, 명령 실행 로깅이 셸의 기본 기능으로 들어올 것이다. Claude Code, Codex CLI 같은 에이전트가 셸을 “두 번째 사용자”로 다루는 시대다.
전망 5 — 코어 도구의 Rust 재작성
ripgrep, fd, eza, bat, bottom, just, mise, Starship — 이미 거의 모든 모던 도구가 Rust다. 2028년쯤이면 “Unix 코어 도구의 Rust 세대 교체”가 완료될 것이다.
전망 6 — POSIX의 자리
POSIX 셸은 사라지지 않는다. init script, Docker alpine, embedded Linux, CI 매트릭스의 최저 공통분모로 살아남는다. 그래서 portable 스크립트는 여전히 #!/bin/sh로 짜는 것이 베스트 프랙티스다.
24장 · 셸 스크립팅 체크리스트 (10가지)
매번 셸 스크립트를 짤 때 확인해야 할 10가지를 정리한다.
- shebang —
#!/usr/bin/env bash또는 portable이면#!/bin/sh. - strict mode —
set -Eeuo pipefail을 둘째 줄에. - IFS —
IFS=$'\n\t'로 공백 포함 파일명에 안전. - mktemp — 임시는 항상
mktemp -d로,trap '...' EXIT로 청소. - 인용 —
$var는 거의 항상"$var"로 인용.${arr[@]}는"${arr[@]}". - 종료 코드 — 함수의 결과는 stdout, 성공은 0, 실패는 non-zero.
- shellcheck — CI에서 의무화. 경고는
# shellcheck disable=...로 명시 무시. - shfmt — 포매팅 일관성. pre-commit hook으로 자동.
- 에러 메시지 — stderr(
>&2)로 보내고 종료 코드 명시. - 문서 — 첫 주석 블록에 사용법, 인자, 의존, 예제.
25장 · 7가지 실전 안티패턴 카탈로그
안티패턴 1 — 인용 없는 변수
# 위험
rm $file # $file에 공백/와일드카드 있으면 폭발
# 안전
rm -- "$file"
안티패턴 2 — set -e만 믿고 pipefail 안 씀
curl ... | jq .x에서 curl이 실패해도 jq가 빈 입력으로 성공하면 전체가 성공으로 보인다.
안티패턴 3 — cd의 결과 검사 안 함
# 위험
cd /tmp/build
rm -rf * # cd 실패하면 현재 디렉터리에서 폭발
# 안전
cd /tmp/build || exit 1
rm -rf -- ./*
안티패턴 4 — [ ]와 [[ ]] 혼용
bash는 [[ ]](키워드)를 쓴다. POSIX 호환이 필요하면 [ ]. 섞지 말 것.
안티패턴 5 — 백틱(`cmd`)
$(cmd)를 쓰자. 중첩 가능, 인용 안전, 가독성 좋음.
안티패턴 6 — eval
사용자 입력을 eval에 넣지 말 것. 거의 모든 경우 다른 방법이 있다(배열, 함수, bash -c "$(...)").
안티패턴 7 — 거대한 단일 스크립트
1000줄 넘어가면 Go/Python으로 재작성을 검토한다. 셸의 강점은 글루(glue) — 다른 도구를 묶는 데 있다. 비즈니스 로직은 다른 언어로.
26장 · 결론 — 셸은 여전히 인프라의 한가운데에 있다
37년 전 Brian Fox가 처음 bash를 발표했을 때, 그는 GNU 프로젝트의 일부로 “자유로운 Bourne shell 대체”를 만들고 있다고 생각했다. 그 한 줄의 결정이 오늘날 거의 모든 컴퓨터의 가장 안쪽에 자리잡았다.
2026년의 셸 풍경을 정리하면:
- Bash 5.2가 표준 — 어디서나 동작한다. 스크립트의 디폴트.
- Zsh 5.9가 macOS 기본 — 인터랙티브 한정.
- fish 4가 Rust로 재탄생 — 친화적 인터랙티브.
- Nushell이 구조화 데이터 셸 — 데이터 엔지니어/클라우드.
- ShellCheck + shfmt + bashly가 모던 워크플로의 핵심.
- fzf, ripgrep, fd, bat, eza, just, mise, Starship이 차세대 도구.
- gum이 셸에 예쁜 TUI를.
- sgpt, Warp, Claude Code가 AI 통합.
- Ghostty, Wezterm, Alacritty, Kitty가 차세대 터미널.
셸은 사라지지 않는다. 셸은 더 풍부해지고, 더 안전해지고, 더 표현력 있게 진화한다. 우리가 할 일은 그 도구를 잘 고르고, strict mode와 shellcheck로 견고하게 짜고, 1000줄을 넘으면 다른 언어로 옮기는 결단을 내리는 것이다.
셸은 운영의 한가운데에 있다. 운영이 사라지지 않는 한, 셸도 사라지지 않는다.
참고 자료(References)
- GNU Bash Manual — https://www.gnu.org/software/bash/manual/
- Bash 5.2 Release Notes — https://lists.gnu.org/archive/html/bug-bash/2022-09/msg00214.html
- Zsh Documentation — https://zsh.sourceforge.io/Doc/
- Oh My Zsh — https://ohmyz.sh/
- fish shell — https://fishshell.com/
- Nushell Book — https://www.nushell.sh/book/
- Murex — https://murex.rocks/
- xonsh — https://xon.sh/
- Elvish — https://elv.sh/
- ShellCheck — https://www.shellcheck.net/
- shfmt(mvdan) — https://github.com/mvdan/sh
- bashly — https://bashly.dannyb.co/
- gum(Charm) — https://github.com/charmbracelet/gum
- fzf — https://github.com/junegunn/fzf
- ripgrep — https://github.com/BurntSushi/ripgrep
- fd — https://github.com/sharkdp/fd
- bat — https://github.com/sharkdp/bat
- eza — https://github.com/eza-community/eza
- bottom — https://github.com/ClementTsang/bottom
- just — https://github.com/casey/just
- taskfile.dev — https://taskfile.dev/
- mise — https://mise.jdx.dev/
- Starship — https://starship.rs/
- Oh My Posh — https://ohmyposh.dev/
- Warp — https://www.warp.dev/
- Ghostty — https://ghostty.org/
- Wezterm — https://wezfurlong.org/wezterm/
- Alacritty — https://alacritty.org/
- POSIX Shell Standard — https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
- Google Shell Style Guide — https://google.github.io/styleguide/shellguide.html