learn_bash_from_session_data 1.0.10 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1266 @@
1
+ """
2
+ Enrichment data for Shell Builtins and File System commands.
3
+
4
+ This module provides additional educational metadata (use_cases, gotchas,
5
+ related commands, difficulty, and supplementary flags) for commands that
6
+ exist in COMMAND_DB but are missing these fields.
7
+
8
+ Sources:
9
+ - GNU Bash Reference Manual (v5.3, 2025): https://www.gnu.org/software/bash/manual/bash.html
10
+ - man7.org Linux man-pages: https://man7.org/linux/man-pages/dir_section_1.html
11
+ - Greg's Wiki / BashPitfalls: https://mywiki.wooledge.org/BashPitfalls
12
+ - Bash Hackers Wiki: https://bash-hackers.gabe565.com/
13
+ """
14
+
15
+ ENRICHMENT_DATA = {
16
+ # =========================================================================
17
+ # SHELL BUILTINS
18
+ # =========================================================================
19
+
20
+ "echo": {
21
+ "man_url": "https://man7.org/linux/man-pages/man1/echo.1.html",
22
+ "use_cases": [
23
+ "Print variable values for debugging: echo \"Value is: $MY_VAR\"",
24
+ "Write content to files: echo 'config_line' >> config.txt",
25
+ "Print colored output in terminals: echo -e '\\033[31mError\\033[0m'",
26
+ "Generate simple text output in scripts for user feedback",
27
+ ],
28
+ "gotchas": [
29
+ "echo -e interpretation varies between shells and systems -- use printf for portable escape handling",
30
+ "echo without quotes on a variable with spaces causes word splitting: echo $var vs echo \"$var\"",
31
+ "echo -n may print literal '-n' in some shells (dash, POSIX sh) instead of suppressing the newline",
32
+ "If the string starts with a dash, echo may interpret it as an option -- use printf '%s\\n' instead",
33
+ ],
34
+ "related": ["printf", "cat", "tee"],
35
+ "difficulty": "beginner",
36
+ "extra_flags": {
37
+ "-E": "Disable interpretation of backslash escapes (default in bash)",
38
+ },
39
+ },
40
+
41
+ "printf": {
42
+ "man_url": "https://man7.org/linux/man-pages/man1/printf.1.html",
43
+ "use_cases": [
44
+ "Portable formatted output in scripts: printf '%s\\n' \"$message\"",
45
+ "Print padded or aligned columns: printf '%-20s %5d\\n' \"$name\" \"$count\"",
46
+ "Generate NUL-delimited output for xargs -0: printf '%s\\0' \"${files[@]}\"",
47
+ "Create formatted log entries with timestamps: printf '[%s] %s\\n' \"$(date)\" \"$msg\"",
48
+ "Safely print strings that may start with dashes (unlike echo)",
49
+ ],
50
+ "gotchas": [
51
+ "printf does NOT add a trailing newline by default -- you must include \\n explicitly",
52
+ "The format string is reused if there are more arguments than format specifiers: printf '%s\\n' a b c prints three lines",
53
+ "Octal escape \\0NNN in printf format differs from echo -e which uses \\NNN",
54
+ "%q format is bash-specific and not POSIX portable",
55
+ ],
56
+ "related": ["echo", "cat", "tee", "seq"],
57
+ "difficulty": "beginner",
58
+ "extra_flags": {
59
+ "-v": "Assign the output to a variable instead of printing to stdout (bash extension)",
60
+ },
61
+ },
62
+
63
+ "read": {
64
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
65
+ "use_cases": [
66
+ "Prompt for user input: read -p 'Enter your name: ' name",
67
+ "Read a password without echoing: read -s -p 'Password: ' pass",
68
+ "Parse delimited data: IFS=: read -r user _ uid gid _ home shell < /etc/passwd",
69
+ "Read a file line by line: while IFS= read -r line; do echo \"$line\"; done < file.txt",
70
+ "Read with a timeout for non-blocking input: read -t 5 -p 'Quick! ' answer",
71
+ ],
72
+ "gotchas": [
73
+ "Without -r, backslashes are treated as escape characters and consumed -- always use read -r for raw input",
74
+ "Without setting IFS='', leading and trailing whitespace is trimmed from the input",
75
+ "Piping into read runs it in a subshell, so variables set inside are lost: echo x | read var (var is empty after)",
76
+ "The correct pattern for file reading is: while IFS= read -r line; do ...; done < file (not cat file | while read)",
77
+ "read returns non-zero at EOF even if it read partial data -- check the variable, not just the exit status",
78
+ ],
79
+ "related": ["echo", "printf", "cat", "select"],
80
+ "difficulty": "beginner",
81
+ "extra_flags": {
82
+ "-a": "Read words into an indexed array variable",
83
+ "-d": "Use specified delimiter instead of newline to terminate input",
84
+ "-n": "Read exactly N characters (does not wait for newline)",
85
+ "-N": "Read exactly N characters, ignoring delimiters",
86
+ "-i": "Use specified text as initial input for readline editing (with -e)",
87
+ "-e": "Use readline for input (allows arrow keys, history in interactive use)",
88
+ "-u": "Read from file descriptor N instead of stdin",
89
+ },
90
+ },
91
+
92
+ "source": {
93
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
94
+ "use_cases": [
95
+ "Reload shell configuration after editing: source ~/.bashrc",
96
+ "Activate a Python virtual environment: source venv/bin/activate",
97
+ "Load environment variables from a .env file: source .env",
98
+ "Include shared function libraries in scripts: source lib/utils.sh",
99
+ ],
100
+ "gotchas": [
101
+ "source executes in the current shell, so any exit call in the sourced file will terminate YOUR shell session",
102
+ "Variables and functions from the sourced file persist in the current shell -- there is no isolation",
103
+ "If the sourced file has a syntax error, it can leave your shell in a broken state",
104
+ "source searches PATH if the filename has no slashes -- use ./file to be explicit about the current directory",
105
+ "The . (dot) command is the POSIX equivalent; source is a bash extension not available in dash or strict POSIX sh",
106
+ ],
107
+ "related": [".", "exec", "eval", "bash"],
108
+ "difficulty": "beginner",
109
+ },
110
+
111
+ ".": {
112
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
113
+ "use_cases": [
114
+ "Load environment variables: . /etc/profile",
115
+ "Include function definitions: . ./lib/helpers.sh",
116
+ "POSIX-compatible alternative to source: . ~/.bashrc",
117
+ ],
118
+ "gotchas": [
119
+ "Identical to source in bash, but . is the POSIX-standard form and works in all POSIX shells",
120
+ "If given a filename without a path component, searches PATH -- use ./ prefix for current directory files",
121
+ "An exit in the sourced file terminates the calling shell",
122
+ ],
123
+ "related": ["source", "exec", "eval"],
124
+ "difficulty": "beginner",
125
+ },
126
+
127
+ "exec": {
128
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
129
+ "use_cases": [
130
+ "Replace shell with another process in Docker entrypoints: exec python app.py",
131
+ "Redirect all subsequent script output to a log file: exec > /var/log/script.log 2>&1",
132
+ "Open a file descriptor for reading: exec 3< input.txt",
133
+ "Replace the current shell with a login shell: exec -l bash",
134
+ "Ensure a wrapper script does not remain as a parent process: exec \"$@\" in entrypoint.sh",
135
+ ],
136
+ "gotchas": [
137
+ "exec replaces the current process -- any code after exec never runs (unless exec fails)",
138
+ "When used for redirection (exec > file), it affects ALL subsequent commands in the shell, not just one",
139
+ "exec without a command but with redirections only changes file descriptors for the current shell",
140
+ "In Docker, failing to use exec means signals (SIGTERM) go to the shell, not your application",
141
+ ],
142
+ "related": ["source", "eval", "bash", "nohup"],
143
+ "difficulty": "intermediate",
144
+ },
145
+
146
+ "eval": {
147
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
148
+ "use_cases": [
149
+ "Initialize ssh-agent in a shell session: eval \"$(ssh-agent -s)\"",
150
+ "Execute dynamically constructed commands from trusted sources",
151
+ "Perform indirect variable expansion in older bash: eval echo \\$$varname",
152
+ "Process output of commands that print shell variable assignments",
153
+ ],
154
+ "gotchas": [
155
+ "eval performs an EXTRA round of expansion before execution -- this is a major injection risk with untrusted input",
156
+ "Never pass user-supplied or external data to eval without rigorous sanitization; attackers can inject arbitrary commands",
157
+ "Prefer bash-native alternatives: ${!varname} for indirect expansion, arrays for dynamic arguments, declare -n for namerefs",
158
+ "Debugging eval is difficult because errors refer to the evaluated string, not your source code line",
159
+ "CVE-2019-9891 was issued for an insecure eval in a widely-used bash scripting guide -- the risk is real",
160
+ ],
161
+ "related": ["exec", "source", "bash"],
162
+ "difficulty": "advanced",
163
+ },
164
+
165
+ "set": {
166
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
167
+ "use_cases": [
168
+ "Make scripts fail-safe: set -euo pipefail at the top of every script",
169
+ "Enable debug tracing: set -x to see each command before it runs, set +x to stop",
170
+ "Reset positional parameters: set -- \"$file1\" \"$file2\" to reassign $1, $2",
171
+ "Disable globbing temporarily: set -f before processing patterns literally",
172
+ ],
173
+ "gotchas": [
174
+ "set -e does NOT cause exit inside if conditions, while/until loops, or commands followed by && or ||",
175
+ "set -e with arithmetic: ((count++)) returns 1 when count is 0, causing unexpected script exit",
176
+ "set -o pipefail makes grep returning no matches (exit 1) fail the entire pipeline",
177
+ "set -u treats ${array[@]} as an unbound variable error on empty arrays in bash < 4.4",
178
+ "set with no arguments prints ALL shell variables and functions -- use set -o to see just options",
179
+ "Use set +o to reset options: save state with oldopts=$(set +o), restore with eval \"$oldopts\"",
180
+ ],
181
+ "related": ["shopt", "trap", "bash"],
182
+ "difficulty": "intermediate",
183
+ "extra_flags": {
184
+ "-a": "Automatically export all subsequently defined or modified variables",
185
+ "-b": "Report terminated background jobs immediately",
186
+ "-n": "Read commands but do not execute them (syntax checking mode)",
187
+ "-v": "Print shell input lines as they are read",
188
+ "-h": "Hash commands as they are looked up for execution",
189
+ "-C": "Prevent output redirection from overwriting existing files (noclobber)",
190
+ "-T": "Inherit DEBUG and RETURN traps in shell functions",
191
+ },
192
+ },
193
+
194
+ "unset": {
195
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
196
+ "use_cases": [
197
+ "Remove a temporary variable after use: unset temp_file",
198
+ "Clear an environment variable to prevent child processes from inheriting it: unset DATABASE_URL",
199
+ "Remove a function definition: unset -f my_helper",
200
+ "Clean up associative array entries: unset 'mymap[key]'",
201
+ ],
202
+ "gotchas": [
203
+ "unset cannot remove readonly variables -- you get an error and the variable persists for the shell's lifetime",
204
+ "unset on a nameref (-n) variable unsets the reference, not the target -- use unset -n to remove the nameref itself",
205
+ "Without -v or -f, unset tries the name as a variable first, then as a function -- be explicit with the flag",
206
+ "Unsetting positional parameters ($1, $2) is not possible with unset -- use shift instead",
207
+ ],
208
+ "related": ["set", "export", "declare", "readonly"],
209
+ "difficulty": "beginner",
210
+ },
211
+
212
+ "export": {
213
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
214
+ "use_cases": [
215
+ "Set PATH for child processes: export PATH=\"$PATH:/usr/local/go/bin\"",
216
+ "Configure application behavior: export NODE_ENV=production",
217
+ "Pass credentials to subcommands: export AWS_PROFILE=staging",
218
+ "List all exported variables: export -p",
219
+ ],
220
+ "gotchas": [
221
+ "export only affects child processes -- it does NOT send variables back to the parent shell",
222
+ "export -n removes the export attribute but keeps the variable defined locally",
223
+ "Variables set without export are shell-local and invisible to commands you run",
224
+ "Exporting in a subshell (pipe, $(...)) does not affect the parent -- the subshell's environment is discarded",
225
+ ],
226
+ "related": ["declare", "set", "unset", "env"],
227
+ "difficulty": "beginner",
228
+ },
229
+
230
+ "declare": {
231
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
232
+ "use_cases": [
233
+ "Create an associative array (dictionary): declare -A config; config[host]='localhost'",
234
+ "Enforce integer-only variables: declare -i count=0; count+=5 gives 5, not '05'",
235
+ "Make a variable readonly: declare -r VERSION='1.0.0'",
236
+ "Force lowercase: declare -l name; name='ALICE' stores 'alice'",
237
+ "List all functions and their definitions: declare -f",
238
+ "Inspect a variable's type: declare -p myvar",
239
+ ],
240
+ "gotchas": [
241
+ "Inside functions, declare creates LOCAL variables by default -- use declare -g to create globals",
242
+ "declare -A requires bash 4.0+ -- not available on macOS default bash (3.2) without upgrading",
243
+ "declare -i makes ALL assignments arithmetic: declare -i x; x=hello sets x to 0 (no error)",
244
+ "declare -n (nameref) requires bash 4.3+ and can cause confusing circular reference errors",
245
+ ],
246
+ "related": ["local", "typeset", "readonly", "export"],
247
+ "difficulty": "intermediate",
248
+ "extra_flags": {
249
+ "-l": "Convert value to lowercase on assignment",
250
+ "-u": "Convert value to uppercase on assignment",
251
+ "-n": "Make the variable a nameref (alias for another variable name)",
252
+ "-g": "Create global variable even when used inside a function",
253
+ "-f": "Restrict action to function names and definitions",
254
+ "-F": "Display only function names (not definitions) when listing",
255
+ "-t": "Give the variable the trace attribute (for debugging with DEBUG trap)",
256
+ },
257
+ },
258
+
259
+ "local": {
260
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
261
+ "use_cases": [
262
+ "Prevent variable leaking in functions: local temp_file=$(mktemp)",
263
+ "Create a local copy of IFS: local IFS=,",
264
+ "Local integer variable: local -i count=0",
265
+ "Local array: local -a items=()",
266
+ ],
267
+ "gotchas": [
268
+ "local can only be used inside a function -- calling it at the top level is an error",
269
+ "local var=$(command) masks the exit status of command -- $? always reflects local's success",
270
+ "In bash, local uses dynamic scoping, not lexical -- called functions can see the caller's locals",
271
+ "local -r creates a readonly local that cannot be unset even within the function",
272
+ ],
273
+ "related": ["declare", "typeset", "readonly"],
274
+ "difficulty": "intermediate",
275
+ },
276
+
277
+ "readonly": {
278
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
279
+ "use_cases": [
280
+ "Define constants: readonly DB_HOST='localhost'",
281
+ "Protect critical variables from accidental modification: readonly PATH",
282
+ "Make a function immutable: readonly -f critical_function",
283
+ "List all readonly variables: readonly -p",
284
+ ],
285
+ "gotchas": [
286
+ "Readonly variables CANNOT be unset -- they persist for the entire shell session",
287
+ "A readonly variable in a parent shell is NOT inherited as readonly by child processes",
288
+ "If you source a file that sets readonly variables, you cannot change them afterward in that session",
289
+ "readonly -a applies to indexed arrays; readonly -A applies to associative arrays",
290
+ ],
291
+ "related": ["declare", "local", "export", "unset"],
292
+ "difficulty": "intermediate",
293
+ },
294
+
295
+ "typeset": {
296
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
297
+ "use_cases": [
298
+ "Ksh-compatible integer variable: typeset -i counter=0",
299
+ "Cross-shell scripting where ksh compatibility is needed",
300
+ "Uppercase variable: typeset -u STATUS; STATUS='ok' stores 'OK'",
301
+ ],
302
+ "gotchas": [
303
+ "typeset is considered deprecated in bash since version 4.0 -- use declare instead",
304
+ "typeset exists in bash primarily for ksh compatibility -- new scripts should avoid it",
305
+ "Functionally identical to declare in bash, but declare is the documented and preferred form",
306
+ ],
307
+ "related": ["declare", "local", "readonly"],
308
+ "difficulty": "intermediate",
309
+ },
310
+
311
+ "alias": {
312
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
313
+ "use_cases": [
314
+ "Shorten frequent commands: alias gs='git status'",
315
+ "Add safety nets: alias rm='rm -i'",
316
+ "List all defined aliases: alias",
317
+ "Create multi-command shortcuts: alias update='sudo apt update && sudo apt upgrade'",
318
+ ],
319
+ "gotchas": [
320
+ "Aliases are NOT expanded in non-interactive shells (scripts) by default -- use shopt -s expand_aliases to enable",
321
+ "Aliases do not accept arguments -- use a function if you need parameters",
322
+ "Aliases are expanded at definition time, not execution time -- redefining a referenced command after the alias has no effect",
323
+ "Alias expansion happens before other expansions, which can cause surprising interactions",
324
+ ],
325
+ "related": ["unalias", "function", "command", "builtin"],
326
+ "difficulty": "beginner",
327
+ },
328
+
329
+ "unalias": {
330
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
331
+ "use_cases": [
332
+ "Remove a specific alias: unalias ll",
333
+ "Clear all aliases in the current session: unalias -a",
334
+ "Undo a safety alias to run the real command: unalias rm",
335
+ ],
336
+ "gotchas": [
337
+ "unalias only affects the current shell session -- aliases in .bashrc will return on next login",
338
+ "unalias on a non-existent alias produces an error unless you redirect stderr",
339
+ "To temporarily bypass an alias without removing it, prefix with backslash: \\rm file",
340
+ ],
341
+ "related": ["alias", "command", "builtin"],
342
+ "difficulty": "beginner",
343
+ },
344
+
345
+ "builtin": {
346
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
347
+ "use_cases": [
348
+ "Call the real cd from inside a cd wrapper function: builtin cd \"$dir\"",
349
+ "Force use of bash's echo instead of /bin/echo: builtin echo 'text'",
350
+ "Avoid infinite recursion when overriding a builtin with a function of the same name",
351
+ ],
352
+ "gotchas": [
353
+ "If the specified name is not a shell builtin, builtin returns a non-zero exit status",
354
+ "builtin bypasses functions and aliases but NOT shell keywords (like if, for, while)",
355
+ "Only works for actual builtins -- for external commands, use command instead",
356
+ ],
357
+ "related": ["command", "enable", "type", "alias"],
358
+ "difficulty": "intermediate",
359
+ },
360
+
361
+ "command": {
362
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
363
+ "use_cases": [
364
+ "Check if a command exists: command -v git >/dev/null 2>&1 && echo 'git found'",
365
+ "Bypass a function or alias: command ls (runs /bin/ls, not an alias)",
366
+ "POSIX-portable alternative to which: command -v python3",
367
+ "Inside a function overriding an external command, call the real one: command grep pattern file",
368
+ ],
369
+ "gotchas": [
370
+ "command -v is preferred over which because which is not POSIX and behaves differently across systems",
371
+ "command bypasses functions and aliases but still runs builtins -- use the external path directly to avoid builtins",
372
+ "command -v returns the path for external commands, 'builtin' for builtins, and 'alias ...' for aliases",
373
+ "command -p uses a default system PATH, which may differ from your custom PATH",
374
+ ],
375
+ "related": ["builtin", "type", "which", "enable"],
376
+ "difficulty": "intermediate",
377
+ },
378
+
379
+ "enable": {
380
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
381
+ "use_cases": [
382
+ "Disable a builtin to use an external version: enable -n echo (then /bin/echo is used)",
383
+ "List all builtins with their enabled/disabled status: enable -a",
384
+ "Load a dynamically loadable builtin: enable -f /path/to/lib builtin_name",
385
+ "Re-enable a previously disabled builtin: enable echo",
386
+ ],
387
+ "gotchas": [
388
+ "Disabling a builtin is per-shell-session only -- it resets on new shell launch",
389
+ "enable -f for loadable builtins requires that bash was compiled with loadable builtin support",
390
+ "Disabling critical builtins (cd, exit) can make the shell session hard to use",
391
+ ],
392
+ "related": ["builtin", "command", "type"],
393
+ "difficulty": "advanced",
394
+ },
395
+
396
+ "hash": {
397
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
398
+ "use_cases": [
399
+ "Clear the command path cache after installing new software: hash -r",
400
+ "See which commands have been cached and their hit counts: hash",
401
+ "Force bash to use a specific path for a command: hash -p /usr/local/bin/python python",
402
+ "Remove a single stale entry: hash -d old_command",
403
+ ],
404
+ "gotchas": [
405
+ "If you install a new version of a command in a different PATH location, bash may still use the old cached path until you run hash -r",
406
+ "hash -r clears the entire table -- there is no way to selectively refresh entries",
407
+ "The hash table is per-shell-session and not shared between terminal windows",
408
+ "In scripts with set -e, hash -d on a command not in the table causes the script to exit",
409
+ ],
410
+ "related": ["command", "type", "which", "enable"],
411
+ "difficulty": "intermediate",
412
+ },
413
+
414
+ "pushd": {
415
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html",
416
+ "use_cases": [
417
+ "Save current directory and jump to another: pushd /tmp",
418
+ "Navigate between project directories: pushd ~/project-a, then pushd ~/project-b",
419
+ "Rotate the directory stack: pushd +2 brings the third entry to the top",
420
+ "Build scripts that work in multiple directories and return cleanly",
421
+ ],
422
+ "gotchas": [
423
+ "pushd prints the entire directory stack after each call, which can be noisy -- redirect to /dev/null if unwanted",
424
+ "pushd with no arguments swaps the top two directories on the stack (like cd -)",
425
+ "The directory stack is per-shell and not shared across terminal sessions",
426
+ "Numeric arguments (+N, -N) count from the top or bottom of the stack, not from 0",
427
+ ],
428
+ "related": ["popd", "dirs", "cd"],
429
+ "difficulty": "intermediate",
430
+ },
431
+
432
+ "popd": {
433
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html",
434
+ "use_cases": [
435
+ "Return to the previous directory after pushd: popd",
436
+ "Remove a specific entry from the directory stack: popd +2",
437
+ "Use in scripts paired with pushd for reliable directory restoration",
438
+ ],
439
+ "gotchas": [
440
+ "popd on an empty stack (only the current directory) produces an error",
441
+ "popd changes the working directory -- use popd -n to remove from the stack without changing dirs",
442
+ "The stack index changes after each pop, so removing multiple entries requires care with indices",
443
+ ],
444
+ "related": ["pushd", "dirs", "cd"],
445
+ "difficulty": "intermediate",
446
+ },
447
+
448
+ "dirs": {
449
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html",
450
+ "use_cases": [
451
+ "View the current directory stack: dirs -v (shows numbered list)",
452
+ "Clear the directory stack: dirs -c",
453
+ "Show full paths instead of tilde abbreviation: dirs -l",
454
+ ],
455
+ "gotchas": [
456
+ "dirs always includes the current working directory as the first entry, even if pushd was never used",
457
+ "dirs -v numbers entries from 0, which matches the +N argument for pushd and popd",
458
+ "The output of dirs without flags is space-separated on one line, which is hard to parse for paths with spaces",
459
+ ],
460
+ "related": ["pushd", "popd", "cd", "pwd"],
461
+ "difficulty": "intermediate",
462
+ },
463
+
464
+ "shopt": {
465
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
466
+ "use_cases": [
467
+ "Enable recursive globbing: shopt -s globstar (then **/*.py matches all Python files)",
468
+ "Case-insensitive globbing: shopt -s nocaseglob",
469
+ "Append to history instead of overwriting: shopt -s histappend",
470
+ "Allow cd to correct minor typos: shopt -s cdspell",
471
+ "Include dotfiles in glob expansion: shopt -s dotglob",
472
+ "Check option status quietly: shopt -q globstar && echo 'on'",
473
+ ],
474
+ "gotchas": [
475
+ "shopt options are different from set options -- shopt -s vs set -o control different settings",
476
+ "globstar (**) in bash < 4.0 is not available; bash on macOS defaults to 3.2",
477
+ "shopt -s failglob causes an error when a glob matches nothing, instead of returning the pattern literally",
478
+ "Some shopt settings affect interactive shells only (like cdspell) and are irrelevant in scripts",
479
+ ],
480
+ "related": ["set", "bash"],
481
+ "difficulty": "intermediate",
482
+ },
483
+
484
+ "bind": {
485
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
486
+ "use_cases": [
487
+ "Enable vi editing mode: bind -m vi",
488
+ "Bind up-arrow to history search: bind '\"\\e[A\": history-search-backward'",
489
+ "List all current key bindings: bind -p",
490
+ "List all available readline functions: bind -l",
491
+ "Load bindings from an inputrc file: bind -f ~/.inputrc",
492
+ ],
493
+ "gotchas": [
494
+ "bind changes only affect the current shell session unless saved in ~/.inputrc",
495
+ "Key binding syntax uses readline notation, which differs from terminal escape codes",
496
+ "bind -x bindings run as shell commands, which can slow down interactive response if complex",
497
+ "Binding conflicts with terminal emulator shortcuts may cause bindings to not work as expected",
498
+ ],
499
+ "related": ["set", "shopt", "history"],
500
+ "difficulty": "advanced",
501
+ },
502
+
503
+ "history": {
504
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-History-Builtins.html",
505
+ "use_cases": [
506
+ "Search previous commands: history | grep ssh",
507
+ "Re-execute command number 42: !42",
508
+ "Delete a sensitive entry: history -d 150",
509
+ "Write current history to file immediately: history -w",
510
+ "Read history from file (sync across terminals): history -r",
511
+ "Clear all history for the session: history -c",
512
+ ],
513
+ "gotchas": [
514
+ "history -c clears the in-memory history but not the file -- use history -c && history -w to clear both",
515
+ "Passwords or secrets typed on the command line are saved in ~/.bash_history -- delete sensitive entries with history -d",
516
+ "By default, history is written when the shell exits -- concurrent terminals can overwrite each other's history",
517
+ "Use HISTCONTROL=ignorespace to prevent commands starting with a space from being saved",
518
+ "HISTSIZE controls in-memory entries; HISTFILESIZE controls on-disk entries -- set both",
519
+ ],
520
+ "related": ["fc", "bind", "shopt"],
521
+ "difficulty": "beginner",
522
+ "extra_flags": {
523
+ "-a": "Append the current session's new history lines to the history file",
524
+ "-n": "Read history lines not already read from the history file into the current list",
525
+ "-w": "Write current history list to the history file (overwrite)",
526
+ "-r": "Read the history file and append its contents to the current history list",
527
+ "-p": "Perform history expansion on the arguments and display the result without storing",
528
+ "-s": "Append the arguments to the history list as a single entry (without executing)",
529
+ },
530
+ },
531
+
532
+ "fc": {
533
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-History-Builtins.html",
534
+ "use_cases": [
535
+ "Edit and re-run the last command in your editor: fc",
536
+ "List the last 10 commands: fc -l -10",
537
+ "Re-execute a command by number without editing: fc -s 42",
538
+ "Quick substitution and re-run: fc -s old=new (replaces 'old' with 'new' in the last command)",
539
+ "Use a specific editor: fc -e vim",
540
+ ],
541
+ "gotchas": [
542
+ "fc with no arguments opens the LAST command in an editor -- saving and quitting immediately re-runs it",
543
+ "The editor used depends on FCEDIT, then EDITOR, then defaults to vi -- make sure you know which one",
544
+ "fc -s with no pattern re-runs the last command, which can be dangerous if the last command was destructive",
545
+ "fc -l without -n includes line numbers, which may confuse copy-paste workflows",
546
+ ],
547
+ "related": ["history", "bind"],
548
+ "difficulty": "intermediate",
549
+ },
550
+
551
+ "true": {
552
+ "man_url": "https://man7.org/linux/man-pages/man1/true.1.html",
553
+ "use_cases": [
554
+ "Create an infinite loop: while true; do process_queue; sleep 1; done",
555
+ "Provide a guaranteed-success command in a pipeline or conditional",
556
+ "Default success action: command || true (suppress failure exit code)",
557
+ "Placeholder for an unfinished function body",
558
+ ],
559
+ "gotchas": [
560
+ "true is both a shell builtin and an external command (/bin/true) -- the builtin is faster",
561
+ "In some contexts, : (colon) is preferred over true as the no-op command because it is always a builtin",
562
+ "true ignores all arguments silently -- true --help still returns 0 (the builtin version)",
563
+ ],
564
+ "related": ["false", ":", "test"],
565
+ "difficulty": "beginner",
566
+ },
567
+
568
+ "false": {
569
+ "man_url": "https://man7.org/linux/man-pages/man1/false.1.html",
570
+ "use_cases": [
571
+ "Test error handling: false || echo 'caught failure'",
572
+ "Force a script to exit under set -e: false",
573
+ "Disable a feature flag: ENABLED=false; if $ENABLED; then ... (note: this runs the false command)",
574
+ ],
575
+ "gotchas": [
576
+ "false always returns exit code 1, not an arbitrary non-zero value",
577
+ "Using $ENABLED where ENABLED=false runs the false command -- this works but is unconventional; prefer [[ $ENABLED == true ]]",
578
+ "false is both a builtin and an external binary; the builtin is used by default",
579
+ ],
580
+ "related": ["true", ":", "exit", "return"],
581
+ "difficulty": "beginner",
582
+ },
583
+
584
+ "test": {
585
+ "man_url": "https://man7.org/linux/man-pages/man1/test.1.html",
586
+ "use_cases": [
587
+ "Check if a file exists: test -f config.yaml && source config.yaml",
588
+ "Compare integers: test $count -gt 0",
589
+ "Check if a variable is set: test -n \"$VAR\"",
590
+ "Check if a string is empty: test -z \"$input\"",
591
+ ],
592
+ "gotchas": [
593
+ "Unquoted variables in test cause errors with spaces or empty values: test -f $file fails if file is empty or has spaces",
594
+ "test uses single = for string comparison (not ==, though bash's test also accepts ==)",
595
+ "Numeric comparisons use -eq, -lt, -gt -- not < or > (which are redirections inside [ ])",
596
+ "test and [ are identical -- [ is not special syntax, it is a command that requires a closing ]",
597
+ ],
598
+ "related": ["[", "[[", "if"],
599
+ "difficulty": "beginner",
600
+ "extra_flags": {
601
+ "-s": "True if file exists and has size greater than zero",
602
+ "-L": "True if file is a symbolic link",
603
+ "-p": "True if file is a named pipe (FIFO)",
604
+ "-S": "True if file is a socket",
605
+ "-O": "True if file is owned by the effective user ID",
606
+ "-G": "True if file is owned by the effective group ID",
607
+ "-nt": "True if file1 is newer than file2 (modification date)",
608
+ "-ot": "True if file1 is older than file2",
609
+ "-ef": "True if file1 and file2 refer to the same inode (hard links)",
610
+ },
611
+ },
612
+
613
+ "[": {
614
+ "man_url": "https://man7.org/linux/man-pages/man1/test.1.html",
615
+ "use_cases": [
616
+ "Conditional file check in if block: if [ -f /etc/hosts ]; then echo 'exists'; fi",
617
+ "String comparison: [ \"$answer\" = 'yes' ]",
618
+ "Numeric comparison: [ $x -le 100 ]",
619
+ "Combine conditions: [ -d dir ] && [ -w dir ]",
620
+ ],
621
+ "gotchas": [
622
+ "[ is a command, not syntax -- there MUST be spaces around [ and ] or you get parse errors",
623
+ "Always quote variables inside [ ]: [ -f $file ] breaks if file is empty or has spaces",
624
+ "Do not use && or || inside [ ] -- use -a and -o, or use separate [ ] tests joined by shell && / ||",
625
+ "< and > inside [ ] are redirection operators, not comparison -- use -lt and -gt for numbers, or use [[ ]] for string comparison",
626
+ "The closing ] is actually an argument to [ -- forgetting it gives 'missing ]' errors",
627
+ ],
628
+ "related": ["test", "[[", "if"],
629
+ "difficulty": "beginner",
630
+ },
631
+
632
+ "[[": {
633
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html",
634
+ "use_cases": [
635
+ "Pattern matching: [[ $filename == *.tar.gz ]]",
636
+ "Regex matching: [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$ ]]",
637
+ "Safe unquoted variable comparison: [[ -z $var ]] (no word splitting inside [[)",
638
+ "Combine conditions naturally: [[ -f file && -r file ]]",
639
+ ],
640
+ "gotchas": [
641
+ "[[ is bash-specific -- it will not work in POSIX sh, dash, or other minimal shells",
642
+ "== inside [[ does PATTERN matching (glob), not exact string comparison -- quote the right side for literal match: [[ $x == \"$y\" ]]",
643
+ "Regex with =~ does not quote the pattern: [[ $s =~ ^[0-9]+$ ]] works, but [[ $s =~ '^[0-9]+$' ]] matches literally",
644
+ "The regex dialect for =~ is ERE (Extended Regular Expressions) from the system's C library, which varies by OS",
645
+ "[[ does not support -a and -o -- use && and || between conditions instead",
646
+ ],
647
+ "related": ["[", "test", "if", "case"],
648
+ "difficulty": "intermediate",
649
+ },
650
+
651
+ "exit": {
652
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
653
+ "use_cases": [
654
+ "End a script with success: exit 0",
655
+ "Signal an error condition: exit 1",
656
+ "Use in error handlers: || { echo 'Failed'; exit 1; }",
657
+ "Exit with the status of the last command: exit $?",
658
+ ],
659
+ "gotchas": [
660
+ "exit in a sourced script terminates the CALLING shell, not just the script -- use return instead in sourced files",
661
+ "Exit codes above 125 have special meanings: 126 = command not executable, 127 = command not found, 128+N = killed by signal N",
662
+ "exit triggers the EXIT trap -- do not call exit inside an EXIT trap handler or you risk infinite recursion",
663
+ "In a subshell (parentheses or pipe), exit only exits the subshell, not the parent script",
664
+ ],
665
+ "related": ["return", "trap", "break"],
666
+ "difficulty": "beginner",
667
+ },
668
+
669
+ "return": {
670
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
671
+ "use_cases": [
672
+ "Return success from a function: return 0",
673
+ "Return error from a function: return 1",
674
+ "Early exit from a sourced script without killing the caller: return",
675
+ "Propagate a command's exit status: command; return $?",
676
+ ],
677
+ "gotchas": [
678
+ "return can only be used inside a function or a sourced script -- anywhere else gives an error",
679
+ "return with no argument returns the exit status of the last command executed",
680
+ "return does NOT print output -- it only sets the exit code; use echo before return to pass data",
681
+ "In a sourced file, return exits the sourcing back to the caller -- it does not exit the shell like exit would",
682
+ ],
683
+ "related": ["exit", "break", "continue"],
684
+ "difficulty": "beginner",
685
+ },
686
+
687
+ "break": {
688
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
689
+ "use_cases": [
690
+ "Exit a loop when a condition is met: [[ $found == true ]] && break",
691
+ "Break out of nested loops: break 2 exits both inner and outer loop",
692
+ "Stop processing after finding a match in a for loop",
693
+ ],
694
+ "gotchas": [
695
+ "break N exits N levels of nested loops -- break 2 exits two loops, not one",
696
+ "break outside a loop causes an error in strict shells and a warning in bash",
697
+ "break in a case statement inside a loop exits the loop, not the case (case uses ;; to end each pattern)",
698
+ ],
699
+ "related": ["continue", "return", "exit"],
700
+ "difficulty": "beginner",
701
+ },
702
+
703
+ "continue": {
704
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
705
+ "use_cases": [
706
+ "Skip an iteration: [[ $file == *.tmp ]] && continue",
707
+ "Skip to the next iteration of an outer loop: continue 2",
708
+ "Filter out unwanted items in a processing loop",
709
+ ],
710
+ "gotchas": [
711
+ "continue N resumes the Nth enclosing loop, not the current one -- continue 2 affects the outer loop",
712
+ "continue outside a loop causes an error in strict shells and a warning in bash",
713
+ "continue does not skip the rest of a pipeline stage -- it skips the rest of the loop body",
714
+ ],
715
+ "related": ["break", "return", "for", "while"],
716
+ "difficulty": "beginner",
717
+ },
718
+
719
+ "shift": {
720
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
721
+ "use_cases": [
722
+ "Process command-line arguments one at a time: while [[ $# -gt 0 ]]; do arg=$1; shift; done",
723
+ "Skip processed options: shift 2 to discard both the flag and its value",
724
+ "Implement custom argument parsing in shell scripts",
725
+ ],
726
+ "gotchas": [
727
+ "shift when there are no positional parameters ($# is 0) returns an error and does nothing",
728
+ "shift N where N is greater than $# also returns an error in bash",
729
+ "$0 (the script name) is never shifted -- shift only affects $1, $2, etc.",
730
+ "Shifted parameters are gone permanently -- save them to variables first if you need them later",
731
+ ],
732
+ "related": ["getopts", "set"],
733
+ "difficulty": "beginner",
734
+ },
735
+
736
+ "getopts": {
737
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
738
+ "use_cases": [
739
+ "Parse script options: while getopts 'vf:o:h' opt; do case $opt in v) verbose=1;; f) file=$OPTARG;; h) usage;; esac; done",
740
+ "Handle options with required arguments: getopts 'f:' (colon means -f requires an argument)",
741
+ "Skip processed options to access remaining arguments: shift $((OPTIND - 1))",
742
+ ],
743
+ "gotchas": [
744
+ "getopts only supports single-character options -- it cannot parse --long-options (use getopt or manual parsing for those)",
745
+ "A colon after a letter means it requires an argument: 'f:' means -f VALUE -- missing the colon makes it a flag",
746
+ "OPTIND must be reset to 1 if you want to parse a different set of arguments or call getopts again",
747
+ "A leading colon in the optstring (':vf:') enables silent error mode, putting the bad option in OPTARG instead of printing an error",
748
+ "getopts stops at the first non-option argument -- options after positional args are not parsed",
749
+ ],
750
+ "related": ["shift", "case", "set"],
751
+ "difficulty": "intermediate",
752
+ },
753
+
754
+ "trap": {
755
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
756
+ "use_cases": [
757
+ "Clean up temporary files on exit: trap 'rm -f $tmpfile' EXIT",
758
+ "Handle Ctrl-C gracefully: trap 'echo Interrupted; cleanup; exit 130' INT",
759
+ "Log script completion: trap 'echo \"Script finished at $(date)\"' EXIT",
760
+ "Debug function entry/exit: trap 'echo \"Entering: $FUNCNAME\"' DEBUG",
761
+ "Reset a trap: trap - INT (restores default signal handling for SIGINT)",
762
+ ],
763
+ "gotchas": [
764
+ "trap on EXIT fires on ANY exit (success, failure, signals) -- but NOT on SIGKILL (kill -9), which cannot be caught",
765
+ "Inside a trap handler, calling exit re-triggers the EXIT trap -- avoid recursive exit calls in EXIT handlers",
766
+ "ERR traps are NOT inherited by functions by default -- use set -E (errtrace) to propagate them",
767
+ "Trap handlers execute in the context of the current shell -- be careful with variable scope",
768
+ "Setting a trap replaces any previous trap for that signal -- you cannot stack multiple handlers",
769
+ "Use signal names (INT, TERM, EXIT) not numbers for portability -- signal numbers vary across operating systems",
770
+ ],
771
+ "related": ["kill", "exit", "set"],
772
+ "difficulty": "intermediate",
773
+ },
774
+
775
+ "ulimit": {
776
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
777
+ "use_cases": [
778
+ "Show all current resource limits: ulimit -a",
779
+ "Increase open file limit for a database server: ulimit -n 65536",
780
+ "Set maximum process stack size: ulimit -s unlimited",
781
+ "Check the maximum number of user processes: ulimit -u",
782
+ "Set core dump file size to unlimited for debugging: ulimit -c unlimited",
783
+ ],
784
+ "gotchas": [
785
+ "A regular user can only LOWER hard limits -- once lowered, only root can raise them back",
786
+ "Soft limits can be raised up to the hard limit value; hard limits can only be lowered (unless root)",
787
+ "ulimit affects the current shell and its children -- it does not persist across sessions unless set in .bashrc or /etc/security/limits.conf",
788
+ "ulimit -n changes are per-process -- each new shell gets its own copy of the limit",
789
+ "The 'unlimited' keyword means no limit, but the kernel may still enforce system-wide maximums",
790
+ ],
791
+ "related": ["sysctl"],
792
+ "difficulty": "intermediate",
793
+ "extra_flags": {
794
+ "-c": "Maximum size of core files created (blocks)",
795
+ "-d": "Maximum size of a process's data segment (kbytes)",
796
+ "-f": "Maximum size of files written by the shell and children (blocks)",
797
+ "-l": "Maximum size that may be locked into memory (kbytes)",
798
+ "-m": "Maximum resident set size (kbytes, not effective on many systems)",
799
+ "-t": "Maximum amount of CPU time (seconds)",
800
+ "-p": "Pipe buffer size (512-byte blocks)",
801
+ },
802
+ },
803
+
804
+ "times": {
805
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
806
+ "use_cases": [
807
+ "Measure total CPU time used by a shell session: times",
808
+ "Check how much user vs system time scripts consumed",
809
+ "Quick profiling to see if your session is CPU-intensive",
810
+ ],
811
+ "gotchas": [
812
+ "times shows cumulative time for the shell AND all child processes, not individual command times -- use time for that",
813
+ "Output has two lines: first line is the shell itself, second line is all child processes",
814
+ "times takes no arguments -- it always reports on the current shell",
815
+ "The values are only meaningful for long-running sessions; short sessions show 0m0.000s",
816
+ ],
817
+ "related": ["time", "ps"],
818
+ "difficulty": "intermediate",
819
+ },
820
+
821
+ "let": {
822
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
823
+ "use_cases": [
824
+ "Increment a counter: let count++",
825
+ "Perform arithmetic assignment: let 'result = a + b'",
826
+ "Multiple operations: let 'x = 5' 'y = x * 2'",
827
+ "Evaluate a condition arithmetically: let 'x > 0' (returns 0 if true)",
828
+ ],
829
+ "gotchas": [
830
+ "let with an expression that evaluates to 0 returns exit code 1 (failure) -- let 'x = 0' returns 1, which triggers set -e",
831
+ "let only handles integer arithmetic -- no floating point support",
832
+ "Quoting is important: let x = 5 fails (spaces make them separate arguments); use let 'x = 5' or let x=5",
833
+ "Prefer (( )) arithmetic syntax in modern bash: (( count++ )) is clearer than let 'count++'",
834
+ ],
835
+ "related": ["expr", "bc"],
836
+ "difficulty": "intermediate",
837
+ },
838
+
839
+ ":": {
840
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html",
841
+ "use_cases": [
842
+ "Infinite loop: while :; do check_status; sleep 5; done",
843
+ "Set default variable values: : ${TIMEOUT:=30} (sets TIMEOUT to 30 if unset)",
844
+ "No-op placeholder in an if branch: if condition; then :; else handle_else; fi",
845
+ "Comment-like usage in legacy scripts (though # is preferred for comments)",
846
+ ],
847
+ "gotchas": [
848
+ ": is a POSIX special builtin -- errors from : can cause a non-interactive shell to exit in strict POSIX mode",
849
+ "Arguments to : ARE expanded (variable expansion, globbing, etc.) even though the result is discarded",
850
+ ": ${VAR:=default} is an idiom for defaults, but : ${VAR:=command $(that runs)} will execute the command substitution",
851
+ "The colon command is faster than true because it is always a builtin with zero overhead",
852
+ ],
853
+ "related": ["true", "false", "test"],
854
+ "difficulty": "intermediate",
855
+ },
856
+
857
+ "compgen": {
858
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html",
859
+ "use_cases": [
860
+ "List all available commands: compgen -c",
861
+ "List all shell aliases: compgen -A alias",
862
+ "List shell variables matching a prefix: compgen -v PATH",
863
+ "Generate completions from a word list: compgen -W 'start stop restart status' -- 'st'",
864
+ "List all builtin commands: compgen -b",
865
+ ],
866
+ "gotchas": [
867
+ "compgen outputs one match per line -- pipe to sort or grep for filtering",
868
+ "The -- before the prefix word is required to separate compgen options from the word being completed",
869
+ "compgen -c lists ALL commands (builtins, aliases, functions, external) -- use -b, -A function, etc. for specific types",
870
+ "compgen is primarily designed for use inside completion functions, but is useful interactively for discovery",
871
+ ],
872
+ "related": ["complete", "compopt", "type"],
873
+ "difficulty": "advanced",
874
+ },
875
+
876
+ "complete": {
877
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html",
878
+ "use_cases": [
879
+ "Register a completion function: complete -F _my_git_complete git",
880
+ "Simple word-list completion: complete -W 'start stop restart' myservice",
881
+ "File completion for a command: complete -f myeditor",
882
+ "Directory-only completion: complete -d mycd",
883
+ "View existing completions: complete -p",
884
+ "Remove a completion: complete -r mycommand",
885
+ ],
886
+ "gotchas": [
887
+ "complete definitions are session-only -- persist them in ~/.bash_completion or /etc/bash_completion.d/",
888
+ "The completion function receives COMP_WORDS, COMP_CWORD, and must populate COMPREPLY array",
889
+ "complete -o filenames tells bash to treat results as filenames (adds trailing slashes to directories)",
890
+ "Overwriting an existing completion replaces it entirely -- there is no way to extend one",
891
+ ],
892
+ "related": ["compgen", "compopt", "bind"],
893
+ "difficulty": "advanced",
894
+ },
895
+
896
+ "compopt": {
897
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html",
898
+ "use_cases": [
899
+ "Enable filename handling mid-completion: compopt -o filenames",
900
+ "Disable space after completion: compopt -o nospace",
901
+ "Switch completion behavior based on context inside a completion function",
902
+ ],
903
+ "gotchas": [
904
+ "compopt can only be called from within a completion function -- it has no effect elsewhere",
905
+ "compopt modifies options for the current completion attempt, not the complete specification permanently",
906
+ "Available options: filenames, nospace, default, dirnames, plusdirs -- not all combinations are useful",
907
+ ],
908
+ "related": ["complete", "compgen"],
909
+ "difficulty": "advanced",
910
+ },
911
+
912
+ # =========================================================================
913
+ # FILE SYSTEM COMMANDS
914
+ # =========================================================================
915
+
916
+ "ln": {
917
+ "man_url": "https://man7.org/linux/man-pages/man1/ln.1.html",
918
+ "use_cases": [
919
+ "Create a symlink to a config file: ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/",
920
+ "Create a version-independent symlink: ln -s python3.11 /usr/local/bin/python3",
921
+ "Replace an existing symlink atomically: ln -sf /new/target linkname",
922
+ "Create a relative symlink: ln -sr ../shared/lib.sh lib.sh",
923
+ ],
924
+ "gotchas": [
925
+ "Hard links cannot cross filesystem boundaries -- use symbolic links (-s) for cross-device linking",
926
+ "Hard links cannot point to directories (to prevent filesystem loops) -- only symlinks can",
927
+ "Symlinks can become dangling (broken) if the target is moved or deleted -- the link still exists but points nowhere",
928
+ "Without -s, ln creates hard links, which share the same inode -- deleting the original does not break the link",
929
+ "ln TARGET LINK_NAME order is the opposite of cp -- the target comes FIRST, the link name comes SECOND",
930
+ ],
931
+ "related": ["readlink", "realpath", "cp", "stat"],
932
+ "difficulty": "intermediate",
933
+ "extra_flags": {
934
+ "-n": "Treat LINK_NAME as a normal file if it is a symlink to a directory (important for overwriting symlinks to dirs)",
935
+ "-t": "Specify the target directory (useful with xargs)",
936
+ "-b": "Make a backup of each existing destination file",
937
+ },
938
+ },
939
+
940
+ "touch": {
941
+ # touch already has use_cases, gotchas, related, difficulty -- only add extra_flags if missing
942
+ "extra_flags": {
943
+ "-t": "Use specified timestamp [[CC]YY]MMDDhhmm[.ss] instead of current time",
944
+ "-r": "Use the timestamps of a reference file instead of current time",
945
+ },
946
+ },
947
+
948
+ "file": {
949
+ "man_url": "https://man7.org/linux/man-pages/man1/file.1.html",
950
+ "use_cases": [
951
+ "Identify file type before processing: file downloaded_file",
952
+ "Check MIME type for HTTP Content-Type: file -i document.pdf",
953
+ "Verify a binary's architecture: file /usr/bin/ls",
954
+ "Batch-check all files in a directory: file *",
955
+ "Determine encoding of a text file: file -i textfile.txt",
956
+ ],
957
+ "gotchas": [
958
+ "file examines content, not the extension -- renaming a PNG to .txt does not fool it",
959
+ "file may not correctly identify files with unusual or corrupted headers",
960
+ "-i on macOS gives different output format than on Linux (macOS uses --mime instead)",
961
+ "The magic database (/usr/share/misc/magic) can be customized but rarely needs to be",
962
+ ],
963
+ "related": ["stat", "ls", "hexdump"],
964
+ "difficulty": "beginner",
965
+ "extra_flags": {
966
+ "-L": "Follow symbolic links",
967
+ "-z": "Try to look inside compressed files",
968
+ "-k": "Keep going after the first match (show all matching types)",
969
+ "--mime-type": "Print only the MIME type without encoding",
970
+ "--mime-encoding": "Print only the MIME encoding",
971
+ },
972
+ },
973
+
974
+ "stat": {
975
+ "man_url": "https://man7.org/linux/man-pages/man1/stat.1.html",
976
+ "use_cases": [
977
+ "View complete file metadata: stat file.txt",
978
+ "Get numeric permissions: stat -c '%a' file.txt",
979
+ "Get file size in bytes: stat -c '%s' file.txt",
980
+ "Check inode number: stat -c '%i' file.txt",
981
+ "Get human-readable modification time: stat -c '%y' file.txt",
982
+ ],
983
+ "gotchas": [
984
+ "stat -c (format) is GNU/Linux syntax; macOS uses stat -f with different format specifiers",
985
+ "stat follows symlinks by default -- use -L to see the link itself (inverted logic from ls)",
986
+ "The output format tokens (like %a, %s, %y) are completely different between Linux and macOS",
987
+ "stat may show different block sizes depending on the filesystem (512-byte vs 4096-byte blocks)",
988
+ ],
989
+ "related": ["ls", "file", "touch", "chmod"],
990
+ "difficulty": "intermediate",
991
+ "extra_flags": {
992
+ "-L": "Follow symlinks (show target file info)",
993
+ "-f": "Display filesystem status instead of file status",
994
+ "-t": "Print information in terse (machine-parseable) form",
995
+ },
996
+ },
997
+
998
+ "basename": {
999
+ "man_url": "https://man7.org/linux/man-pages/man1/basename.1.html",
1000
+ "use_cases": [
1001
+ "Extract filename from a full path: basename /home/user/documents/report.pdf",
1002
+ "Strip a known extension: basename report.pdf .pdf gives 'report'",
1003
+ "Get script name inside a script: script_name=$(basename \"$0\")",
1004
+ "Process multiple files with -a: basename -a /path/to/file1 /path/to/file2",
1005
+ ],
1006
+ "gotchas": [
1007
+ "basename is purely string manipulation -- it does not check if the file exists",
1008
+ "For extension stripping, the suffix must match exactly: basename file.tar.gz .gz gives 'file.tar', not 'file'",
1009
+ "In bash, parameter expansion can replace basename: ${path##*/} is faster than $(basename \"$path\")",
1010
+ "basename with a trailing slash on a directory path still returns the directory name",
1011
+ ],
1012
+ "related": ["dirname", "realpath", "readlink"],
1013
+ "difficulty": "beginner",
1014
+ "extra_flags": {
1015
+ "-a": "Support multiple arguments (print basename of each)",
1016
+ "-z": "End each output line with NUL instead of newline (for use with xargs -0)",
1017
+ },
1018
+ },
1019
+
1020
+ "dirname": {
1021
+ "man_url": "https://man7.org/linux/man-pages/man1/dirname.1.html",
1022
+ "use_cases": [
1023
+ "Get the parent directory of a file: dirname /home/user/file.txt gives /home/user",
1024
+ "Navigate relative to the script's location: cd \"$(dirname \"$0\")\"",
1025
+ "Construct sibling paths: config=$(dirname \"$0\")/config.ini",
1026
+ ],
1027
+ "gotchas": [
1028
+ "dirname is purely string manipulation -- it does not check if the directory exists or resolve symlinks",
1029
+ "dirname of a bare filename (no slashes) returns '.' (the current directory)",
1030
+ "In bash, parameter expansion can replace dirname: ${path%/*} is faster than $(dirname \"$path\")",
1031
+ "dirname on a path ending with / may return the same path or the parent depending on trailing slash handling",
1032
+ ],
1033
+ "related": ["basename", "realpath", "readlink", "pwd"],
1034
+ "difficulty": "beginner",
1035
+ "extra_flags": {
1036
+ "-z": "End each output line with NUL instead of newline",
1037
+ },
1038
+ },
1039
+
1040
+ "realpath": {
1041
+ "man_url": "https://man7.org/linux/man-pages/man1/realpath.1.html",
1042
+ "use_cases": [
1043
+ "Get the absolute path of a file: realpath ../relative/file.txt",
1044
+ "Resolve all symlinks: realpath /usr/bin/python",
1045
+ "Normalize a messy path: realpath /home/user/../user/./docs",
1046
+ "Check if symlinks resolve without existing: realpath -m /potentially/missing/path",
1047
+ ],
1048
+ "gotchas": [
1049
+ "realpath may not be available on older systems or macOS without coreutils -- use readlink -f as an alternative",
1050
+ "realpath -e fails if any path component does not exist; realpath without -e resolves what it can",
1051
+ "realpath is not a POSIX standard utility, so it may be missing on minimal systems",
1052
+ "On macOS, install coreutils via Homebrew to get GNU realpath (grealpath)",
1053
+ ],
1054
+ "related": ["readlink", "dirname", "basename", "pwd"],
1055
+ "difficulty": "beginner",
1056
+ "extra_flags": {
1057
+ "--relative-to": "Print the path relative to a specified directory",
1058
+ "--relative-base": "Print relative paths if both are descendants of the given base, otherwise absolute",
1059
+ "-q": "Quiet mode -- suppress most error messages",
1060
+ },
1061
+ },
1062
+
1063
+ "readlink": {
1064
+ "man_url": "https://man7.org/linux/man-pages/man1/readlink.1.html",
1065
+ "use_cases": [
1066
+ "Find what a symlink points to: readlink /usr/bin/python3",
1067
+ "Resolve the full canonical path: readlink -f /usr/local/bin/node",
1068
+ "Get the real path of the current script: readlink -f \"$0\"",
1069
+ "Debug broken symlinks by seeing the stored target",
1070
+ ],
1071
+ "gotchas": [
1072
+ "readlink without -f only shows the direct target of a symlink -- it does not resolve chains of symlinks",
1073
+ "readlink -f is equivalent to realpath on most systems, but realpath is more portable in GNU/Linux",
1074
+ "readlink on a non-symlink file prints nothing and returns 1 (without -f, -e, or -m flags)",
1075
+ "readlink -f on macOS requires GNU coreutils (greadlink) -- the BSD readlink does not support -f",
1076
+ ],
1077
+ "related": ["realpath", "ln", "stat", "file"],
1078
+ "difficulty": "intermediate",
1079
+ },
1080
+
1081
+ "rmdir": {
1082
+ "man_url": "https://man7.org/linux/man-pages/man1/rmdir.1.html",
1083
+ "use_cases": [
1084
+ "Safely remove an empty directory: rmdir temp/",
1085
+ "Remove a chain of empty parent directories: rmdir -p a/b/c removes c, then b, then a (if all empty)",
1086
+ "Clean up after a build: rmdir -v build/ obj/ (verbose output shows what was removed)",
1087
+ "Use in scripts as a safe alternative to rm -r when you only want to remove empty dirs",
1088
+ ],
1089
+ "gotchas": [
1090
+ "rmdir REFUSES to delete a directory that contains any files (including hidden files) -- this is a safety feature",
1091
+ "rmdir -p stops at the first non-empty parent; it does not error unless all directories fail",
1092
+ "rmdir does not have a -f (force) option -- if it cannot remove the dir, it always reports an error",
1093
+ "Hidden files (dotfiles) count as contents -- rmdir fails even if the directory appears empty in ls (without -a)",
1094
+ ],
1095
+ "related": ["rm", "mkdir", "find"],
1096
+ "difficulty": "beginner",
1097
+ },
1098
+
1099
+ "locate": {
1100
+ "man_url": "https://man7.org/linux/man-pages/man1/locate.1.html",
1101
+ "use_cases": [
1102
+ "Find files instantly by name: locate nginx.conf",
1103
+ "Case-insensitive search: locate -i readme",
1104
+ "Count matching files: locate -c '*.log'",
1105
+ "Limit output: locate -n 5 pattern",
1106
+ ],
1107
+ "gotchas": [
1108
+ "locate uses a database (updated by updatedb) that may be hours or days old -- recently created files may not appear",
1109
+ "Run sudo updatedb to refresh the database before searching if you need current results",
1110
+ "locate returns files that have been DELETED since the last updatedb run -- they no longer exist on disk",
1111
+ "The database typically excludes certain directories (like /tmp, /proc) and network mounts",
1112
+ "On many modern systems, mlocate or plocate has replaced locate with incremental updates",
1113
+ ],
1114
+ "related": ["find", "updatedb", "which", "whereis"],
1115
+ "difficulty": "beginner",
1116
+ },
1117
+
1118
+ "which": {
1119
+ "man_url": "https://linux.die.net/man/1/which",
1120
+ "use_cases": [
1121
+ "Find where a command lives: which python3",
1122
+ "Check if a command is installed: which docker || echo 'docker not found'",
1123
+ "Find all versions in PATH: which -a python",
1124
+ "Verify which version runs by default: which node",
1125
+ ],
1126
+ "gotchas": [
1127
+ "which only searches PATH for external executables -- it does not find shell builtins, functions, or aliases",
1128
+ "Prefer 'command -v' or 'type' in scripts -- which is not POSIX standard and behavior varies across systems",
1129
+ "On some systems, which is a shell script that parses alias output, which can give unexpected results",
1130
+ "which may show different results than what actually runs if there are aliases or functions shadowing the command",
1131
+ ],
1132
+ "related": ["whereis", "type", "command", "hash"],
1133
+ "difficulty": "beginner",
1134
+ },
1135
+
1136
+ "whereis": {
1137
+ "man_url": "https://man7.org/linux/man-pages/man1/whereis.1.html",
1138
+ "use_cases": [
1139
+ "Find binary, source, and man page locations: whereis gcc",
1140
+ "Find only the binary: whereis -b python",
1141
+ "Find only man pages: whereis -m bash",
1142
+ "Discover installed documentation: whereis -m ls",
1143
+ ],
1144
+ "gotchas": [
1145
+ "whereis searches hardcoded system directories, not your PATH -- it may miss binaries in non-standard locations",
1146
+ "whereis may return multiple results if the same name exists in different standard locations",
1147
+ "Unlike which, whereis does not tell you which binary would actually run -- it just lists known locations",
1148
+ "whereis is not available on all systems (it comes from util-linux on Linux)",
1149
+ ],
1150
+ "related": ["which", "type", "command", "locate", "man"],
1151
+ "difficulty": "beginner",
1152
+ },
1153
+
1154
+ "type": {
1155
+ "man_url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html",
1156
+ "use_cases": [
1157
+ "Determine how a command name is resolved: type ls (shows alias, builtin, or path)",
1158
+ "Get just the type classification: type -t cd gives 'builtin'",
1159
+ "Show all interpretations: type -a echo shows both the builtin and /usr/bin/echo",
1160
+ "Check if something is a function: type -t my_func gives 'function'",
1161
+ ],
1162
+ "gotchas": [
1163
+ "type is a bash builtin that shows how BASH would resolve a name -- including aliases, functions, builtins, and PATH lookup",
1164
+ "type -t returns one of: alias, keyword, function, builtin, file -- useful for conditional logic in scripts",
1165
+ "Unlike which, type knows about shell internals (builtins, aliases, functions) and is always correct for the current shell",
1166
+ "type -a can show that a name resolves multiple ways -- e.g., echo is both a builtin and an external binary",
1167
+ ],
1168
+ "related": ["which", "command", "whereis", "hash"],
1169
+ "difficulty": "beginner",
1170
+ },
1171
+
1172
+ "less": {
1173
+ "man_url": "https://man7.org/linux/man-pages/man1/less.1.html",
1174
+ "use_cases": [
1175
+ "View a large file with scrolling: less /var/log/syslog",
1176
+ "Search within a file: press / then type a pattern",
1177
+ "View command output: ps aux | less",
1178
+ "Follow a growing file (like tail -f): less +F logfile.log",
1179
+ "View multiple files: less file1.txt file2.txt (use :n and :p to navigate)",
1180
+ ],
1181
+ "gotchas": [
1182
+ "less loads files lazily and does not read the entire file into memory -- ideal for huge files",
1183
+ "Press q to quit, not Ctrl-C (Ctrl-C interrupts a search or input, it does not exit)",
1184
+ "Use -R to properly display colored output from commands like git diff or grep --color",
1185
+ "less interprets some escape codes by default; use -r (raw) for full binary passthrough, or -R for ANSI only",
1186
+ "Setting LESSOPEN/LESSCLOSE can make less interpret compressed files, PDFs, etc.",
1187
+ ],
1188
+ "related": ["more", "cat", "head", "tail"],
1189
+ "difficulty": "beginner",
1190
+ "extra_flags": {
1191
+ "-F": "Quit immediately if the entire file fits on one screen",
1192
+ "-X": "Don't clear the screen on exit (leave content visible)",
1193
+ "-i": "Case-insensitive search (unless pattern has uppercase)",
1194
+ "-g": "Highlight only the current search match (not all matches)",
1195
+ "+G": "Start at the end of the file",
1196
+ },
1197
+ },
1198
+
1199
+ "more": {
1200
+ "man_url": "https://man7.org/linux/man-pages/man1/more.1.html",
1201
+ "use_cases": [
1202
+ "View a file one screen at a time: more file.txt",
1203
+ "Pipe long output for pagination: dmesg | more",
1204
+ "Start at a specific line: more +100 file.txt",
1205
+ ],
1206
+ "gotchas": [
1207
+ "more only scrolls forward, not backward -- use less if you need to scroll up",
1208
+ "more exits automatically when you reach the end of the file",
1209
+ "On most modern Linux systems, more is actually a simplified version of less",
1210
+ "more does not support searching as well as less does",
1211
+ ],
1212
+ "related": ["less", "cat", "head", "tail"],
1213
+ "difficulty": "beginner",
1214
+ "extra_flags": {
1215
+ "-d": "Display help message at the bottom instead of ringing bell on illegal key",
1216
+ "-s": "Squeeze multiple blank lines into one",
1217
+ "+/pattern": "Start displaying at the first line matching the pattern",
1218
+ },
1219
+ },
1220
+
1221
+ "clear": {
1222
+ "man_url": "https://man7.org/linux/man-pages/man1/clear.1.html",
1223
+ "use_cases": [
1224
+ "Clear the terminal screen: clear",
1225
+ "Clear screen in a script for clean output presentation",
1226
+ "Clear screen and scrollback buffer: clear -x (if supported)",
1227
+ ],
1228
+ "gotchas": [
1229
+ "clear sends terminal escape codes -- it does not actually delete anything from the scrollback on all terminals",
1230
+ "Ctrl-L in bash also clears the screen but preserves the current command line (different from running clear)",
1231
+ "In scripts, printf '\\033[2J\\033[H' is a more portable way to clear the screen",
1232
+ "Some terminal emulators support clear -x to also clear the scrollback buffer",
1233
+ ],
1234
+ "related": ["reset", "tput"],
1235
+ "difficulty": "beginner",
1236
+ "extra_flags": {
1237
+ "-T": "Specify the terminal type to use instead of the TERM environment variable",
1238
+ "-V": "Print version information",
1239
+ },
1240
+ },
1241
+
1242
+ "man": {
1243
+ "man_url": "https://man7.org/linux/man-pages/man1/man.1.html",
1244
+ "use_cases": [
1245
+ "Read the manual for a command: man ls",
1246
+ "Search for a keyword across all man pages: man -k compression",
1247
+ "View a specific section: man 3 printf (C library function, not shell command)",
1248
+ "View the man page for man itself: man man",
1249
+ "Display a one-line description: man -f ls (equivalent to whatis)",
1250
+ ],
1251
+ "gotchas": [
1252
+ "Man page sections matter: man printf shows the shell command; man 3 printf shows the C function",
1253
+ "man -k requires the mandb database to be built -- run sudo mandb if -k returns nothing",
1254
+ "Man pages are displayed using a pager (usually less) -- all less keybindings work inside man",
1255
+ "Some builtins (like cd, source) do not have their own man page -- use man bash and search within it",
1256
+ "MANPATH controls where man looks; if it is incorrectly set, man may not find pages",
1257
+ ],
1258
+ "related": ["info", "help", "apropos", "whatis"],
1259
+ "difficulty": "beginner",
1260
+ "extra_flags": {
1261
+ "-a": "Display all matching man pages in sequence, not just the first",
1262
+ "-w": "Print the location of the man page file instead of displaying it",
1263
+ "-K": "Search for a string in all man pages (slow full-text search)",
1264
+ },
1265
+ },
1266
+ }