-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmdchamp
More file actions
executable file
·4254 lines (4077 loc) · 382 KB
/
cmdchamp
File metadata and controls
executable file
·4254 lines (4077 loc) · 382 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# cmdchamp — CLI trainer (single file, no deps beyond bash 4.3+ / bwrap)
#
# FILE MAP (search for ═══ SECTION ═══ markers):
# INIT .............. bash check, XDG, colors, constants
# PROFILE ........... load/save, first run, intro, tutorial
# BOSSES & LEVELS ... name arrays, scenario metadata
# SANDBOX ........... file generators, bwrap exec, state checks
# SESSION ........... cleanup trap, stty restore, session summary
# FLOPPY DISKS ...... 8 hidden collectibles
# VARIABLE POOLS .... randomization data (logs, configs, IPs, etc.)
# HELPERS ........... _fpick, _frnum, _ctx, _QV
# MANPAGES & EXP .... inline manpage data, explain() renderer
# QUESTION DSL ...... _qparse, format docs
# LEVEL GENERATORS .. gen_level1–30 (question content)
# SCENARIOS ......... 8 sc_setup/sc_steps functions
# ANSWER CHECK ...... _fnorm, check, load/save
# SCORING & UI ...... _hash, norm, stats, hdr, qdisp, draw
# VI HANDLER ........ _vi_normal, _read_line (shared input)
# SCORING ENGINE .... _sset, _sflush, _sget, decay, _mode_init
# RUN ENGINE ........ run(), _run_loop
# POST-ROOT ......... challenge, scenario, placement
# SCENARIO ENGINE ... scenario(), snapshot/rollback
# SELFTEST .......... _selftest (unit tests)
# PLACEMENT ......... place() (skip-ahead test)
# CLI ENTRYPOINT .... arg parsing, dispatch
# ═══ INIT ═══
set -uo pipefail
[[ ${BASH_VERSINFO[0]:-0} -lt 4 || (${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]:-0} -lt 3) ]] && { echo "Requires bash 4.3+"; exit 1; }
_tty() { [[ -t 0 ]] || { echo "Error: requires interactive terminal" >&2; exit 1; }; }
command -v awk &>/dev/null || { echo "Error: awk not found" >&2; exit 1; }
DATA="${XDG_DATA_HOME:-$HOME/.local/share}/cmdchamp"; mkdir -p "$DATA"; touch "$DATA/scores"
R=$'\e[31m' G=$'\e[32m' Y=$'\e[33m' U=$'\e[1;34m' M=$'\e[35m' C=$'\e[36m' W=$'\e[37m' B=$'\e[1m' N=$'\e[0m' D=$'\e[2m'
_SY=$'\e[?2026h' _SN=$'\e[?2026l'
_trim() { REPLY="${1#"${1%%[![:space:]]*}"}"; REPLY="${REPLY%"${REPLY##*[![:space:]]}"}"; }
_csv_count() { [[ -z "$1" ]] && { REPLY=0; return; }; IFS=',' read -ra _cca <<< "$1"; REPLY=${#_cca[@]}; }
_strip_ansi() {
local s="$1" out="" i=0 ch
while ((i < ${#s})); do
ch="${s:i:1}"
if [[ "$ch" == $'\e' ]]; then
((i++))
if ((i < ${#s})) && [[ "${s:i:1}" == "[" ]]; then
((i++))
while ((i < ${#s})) && [[ ! "${s:i:1}" =~ [a-zA-Z] ]]; do ((i++)); done
((i < ${#s})) && ((i++))
fi
else out+="$ch"; ((i++)); fi
done
REPLY="$out"
}
VERSION="1.1.0"
SANDBOX_TIMEOUT=5 # Seconds before sandbox command times out
BOSS_THRESHOLD=4 # Correct answers needed to beat boss (out of BOSS_TOTAL)
BOSS_TOTAL=5 # Number of questions in boss round
FIRE_STREAK=5 # Streak needed for fire mode (+2 points)
MIX_PER_LEVEL=2 # Questions to auto-mix from each previous level
MAX_LEVEL=30 # Highest level number
BOSS_TIMER=15 # Seconds per boss question
_DECAY_T2=2592000 # 30 days: mastered → learning
_DECAY_T1=1209600 # 14 days: learning → unseen
_DECAY_INTERVAL=86400 # only run decay once per 24h
# ═══ PROFILE ═══
PLAYER_NAME="" BOSS_BEATEN=0 BEST_CHALLENGE=0 DISKS_FOUND="" SC_DONE=""
OPT_VI=1 OPT_SOUND=1 OPT_ALTS=1
_PROFILE_VER=5 # bump this when profile format changes; old profiles get reset
LAST_DECAY=0 # epoch of last decay check (persisted in profile)
PLACED_THROUGH=0 # highest level passed via placement test
_load_profile() {
if [[ -f "$DATA/profile" ]]; then
local _file_ver=0
while IFS='=' read -r key val; do
[[ "$key" =~ ^[A-Z_]+$ ]] || continue
case "$key" in
PLAYER_NAME) PLAYER_NAME="$val";;
BOSS_BEATEN) BOSS_BEATEN="$val";;
BEST_CHALLENGE|BEST_GAUNTLET) BEST_CHALLENGE="$val";;
DISKS_FOUND|EGGS_FOUND) DISKS_FOUND="$val";;
SC_DONE) SC_DONE="$val";;
LAST_DECAY) LAST_DECAY="$val";;
OPT_VI) OPT_VI="$val";;
OPT_SOUND) OPT_SOUND="$val";;
OPT_ALTS) OPT_ALTS="$val";;
PLACED_THROUGH) PLACED_THROUGH="$val";;
PROFILE_VER) _file_ver="$val";;
esac
done < "$DATA/profile"
if ((_file_ver == 2 || _file_ver == 3)); then
# v2→v4, v3→v4: add option fields, migrate in place
_save_profile
elif ((_file_ver != _PROFILE_VER)); then
[[ -f "$DATA/profile" ]] && cp "$DATA/profile" "$DATA/profile.bak"
[[ -f "$DATA/scores" ]] && cp "$DATA/scores" "$DATA/scores.bak"
printf '%s\n' "Profile version mismatch (got $_file_ver, want $_PROFILE_VER) — reset. Backup in profile.bak/scores.bak"
PLAYER_NAME="" BOSS_BEATEN=0 BEST_CHALLENGE=0 DISKS_FOUND="" SC_DONE=""
: > "$DATA/scores"
rm -f "$DATA"/*.json
fi
[[ "$BOSS_BEATEN" =~ ^[0-9]+$ ]] || BOSS_BEATEN=0
((BOSS_BEATEN > MAX_LEVEL)) && BOSS_BEATEN=$MAX_LEVEL
[[ "$BEST_CHALLENGE" =~ ^[0-9]+$ ]] || BEST_CHALLENGE=0
fi
}
_save_profile() {
printf 'PLAYER_NAME=%s\nBOSS_BEATEN=%d\nBEST_CHALLENGE=%d\nDISKS_FOUND=%s\nSC_DONE=%s\nLAST_DECAY=%d\nOPT_VI=%d\nOPT_SOUND=%d\nOPT_ALTS=%d\nPLACED_THROUGH=%d\nPROFILE_VER=%d\n' \
"$PLAYER_NAME" "$BOSS_BEATEN" "$BEST_CHALLENGE" "$DISKS_FOUND" "$SC_DONE" "$LAST_DECAY" "$OPT_VI" "$OPT_SOUND" "$OPT_ALTS" "$PLACED_THROUGH" "$_PROFILE_VER" > "$DATA/profile.tmp"
mv "$DATA/profile.tmp" "$DATA/profile"
}
_first_run() {
printf '%s\e[2J\e[H%s' "$_SY" "$_SN"
echo
read -rp " Enter your handle: " PLAYER_NAME
_strip_ansi "$PLAYER_NAME"; PLAYER_NAME="$REPLY"
PLAYER_NAME="${PLAYER_NAME//$'\n'/}" # strip newlines
PLAYER_NAME="${PLAYER_NAME//$'\r'/}" # strip carriage returns
PLAYER_NAME="${PLAYER_NAME:0:20}" # truncate to 20 chars
[[ -z "$PLAYER_NAME" ]] && PLAYER_NAME="anon"
BOSS_BEATEN=0
_save_profile
_intro
_tutorial
place
}
_intro() {
printf '%s\e[2J\e[H%s' "$_SY" "$_SN"
local pad=$((25 - ${#PLAYER_NAME})); ((pad < 0)) && pad=0
local spaces; printf -v spaces '%*s' "$pad" ''
cat <<EOF
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░ ░░
░░ The networks are dead. The sky is ash. ░░
░░ What remains runs on bone and silicon. ░░
░░ ░░
░░ You are ${W}${B}${PLAYER_NAME}${N}. You have only your shell.${spaces}░░
░░ ░░
░░ 30 daemons guard the descent to ROOT. ░░
░░ ░░
░░ ${D}Know the words, or be forgotten.${N} ░░
░░ ░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
EOF
[[ "${1:-}" == nopause ]] && return
read -rsp " Press Enter to continue..."
echo
}
_tutorial() {
printf '%s\e[2J\e[H\n%s' "$_SY" "$_SN"
cat <<EOF
${U}Controls${N}
${C}Tab${N} Manpage ${C}Enter${N} Submit ${C}Ctrl+d${N} Quit
${U}Vi Mode${N} ${D}(toggle in Options)${N}
${C}Esc${N} Normal mode ${C}i a${N} Insert mode
${C}h l${N} Move cursor ${C}w b${N} Word jump
${C}d${N}w ${C}c${N}w Delete/change word ${C}?${N} Full vi help
EOF
read -rsp " Press Enter to continue..."
echo
}
# ═══ BOSSES & LEVELS ═══
BOSS_NAMES=("" "n00b" "CopyPasta" "Clobber" "CatMan" "PipeWrench" "Murmur" "DevNull" "AndOr" "\$VARIABLE" "\$\$"
"BackgroundNoise" "TestCase" "Gatekeeper" "Daemon" "Mux" "Needle" "Globber" "Sieve" "ArrayLord" "TheFork"
"Swarm" "Regex" "Rebase" "Netcat" "TheFirewall" "Spectrum" "Collision" "Void" "SUID" "ROOT")
BOSS_FLAVOR=("" "There is no shortcut. Only the first step." "Every copy is a ghost of the original." "What you saved, I devour."
"I read the bones of dead files." "Data is blood. I am the vein." "I feed the silence into your commands."
"Your stderr falls into the void." "True or false. Live or die." "I hold what you've forgotten."
"Every process has a death." "I wait in the dark between processes." "Pass my test or be terminated."
"Every door has a lock. I hold the modes." "I run while you sleep forever." "One terminal. Infinite cages."
"I see every line. Nothing hides from me." "I match everything. I consume all." "What passes through me is all that remains."
"Count from zero. End at null." "Every path diverges. None return." "A thousand commands. One directive."
"I see patterns in your ashes." "Your history is mine to rewrite." "I listen where you cannot see."
"Nothing enters. Nothing leaves." "Every frequency bleeds secrets." "Your hashes are already broken."
"I am where data goes to die." "I wear the mask of root." "I am ROOT. Bow or break.")
LEVEL_NAMES=("" "First Steps" "File Basics" "Save Your Work" "Reading Files" "Basic Pipes"
"Input & Here-Strings" "Error Handling" "Logic Gates" "Variables" "Special Variables" "Job Control"
"Test Conditions" "Core File Tools" "System Admin" "Multiplexers" "Text Search"
"File Finding" "Data Processing" "String & Arrays" "Control Flow" "Batch Ops"
"Advanced Regex" "Git" "Network Tools" "Network Scanning" "WiFi & RF"
"Hash Cracking" "Forensics" "Privilege Escalation" "ROOT")
SC_TOTAL=8
SC_NAMES=("" "Permission Lockout" "Archive & Extract" "Find the Needle" "Messy CSV"
"The Incident" "The Broken Deploy" "Log Emergency" "Config Surgery")
SC_UNLOCK=("" 13 13 16 18 18 21 21 21) # boss level needed to unlock each scenario
SC_FLAVOR=(""
"Everything is locked down. Restore access."
"Package it up, ship it out, bring it back."
"Hidden markers are buried in the codebase. Hunt them down."
"Someone dumped messy data. Clean it up."
"An attacker hit the server. Investigate the breach."
"The deploy pipeline is broken. Find the problems and fix them."
"The logs are screaming. Triage the errors before it gets worse."
"The config file is a mess. Perform surgery.")
_victory() {
printf '%s\e[2J\e[H' "$_SY"
local -A sc; [[ -f "$DATA/scores" ]] && while IFS='|' read -r k v; do [[ "$k" == "#"* ]] && continue; sc[$k]=$v; done < "$DATA/scores"
local total=0 mastered=0
for k in "${!sc[@]}"; do
((++total))
local _tier="${sc[$k]%%|*}"
[[ "$_tier" != "0" && "$_tier" != "1" ]] && ((++mastered))
done
local s
cat <<'EOF'
██████╗ ██████╗ ██████╗ ████████╗
██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝
██████╔╝██║ ██║██║ ██║ ██║
██╔══██╗██║ ██║██║ ██║ ██║
██║ ██║╚██████╔╝╚██████╔╝ ██║
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝
EOF
printf -v s '%*s' $(( (70 - 20 - ${#PLAYER_NAME}) / 2 )) ''
printf '%s%s\n\n' "$s" "${G}${B}ACCESS GRANTED: ${W}${PLAYER_NAME}${N}"
local stat_text pad_len stat_pad
printf -v stat_text '%s prompts seen. %s mastered.' "$total" "$mastered"
pad_len=$((56 - ${#stat_text}))
printf -v stat_pad '%*s' "$pad_len" ''
local br="${D}░░${N}" edge="${D}░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░${N}"
printf '\n'
printf ' %s\n' "$edge"
printf ' %s %s\n' "$br" "$br"
printf ' %s The daemons are silent. The descent is complete. %s\n' "$br" "$br"
printf ' %s What remains bends to your command. %s\n' "$br" "$br"
printf ' %s %s\n' "$br" "$br"
printf ' %s 30 bosses defeated. The shell is yours. %s\n' "$br" "$br"
printf ' %s %s%s%s\n' "$br" "$stat_text" "$stat_pad" "$br"
printf ' %s %s\n' "$br" "$br"
printf ' %s %sYou are ROOT.%s %s\n' "$br" "${B}" "${N}" "$br"
printf ' %s %s\n' "$br" "$br"
printf ' %s\n' "$edge"
echo
printf '%s' "$_SN"
[[ "${1:-}" == nopause ]] && return
read -rsp " Press Enter to exit..."
echo
}
_boss_splash() {
local lv=$1
local boss="${BOSS_NAMES[$lv]}"
local flavor="${BOSS_FLAVOR[$lv]}"
printf '%s\e[2J\e[H\n\n' "$_SY"
local s info
printf -v s '%*s' $(( (80 - 12) / 2 )) ''
printf '%s%s\n\n' "$s" "${M}${B}═══ BOSS ═══${N}"
printf -v s '%*s' $(( (80 - ${#boss}) / 2 )) ''
printf '%s%s\n\n' "$s" "${M}${B}${boss}${N}"
printf -v s '%*s' $(( (80 - ${#flavor} - 2) / 2 )) ''
printf '%s%s\n\n' "$s" "${D}\"${flavor}\"${N}"
printf -v info 'No manpages. %d/%d to pass.' "$BOSS_THRESHOLD" "$BOSS_TOTAL"
printf -v s '%*s' $(( (80 - ${#info}) / 2 )) ''
printf '%s%s\n' "$s" "${D}${info}${N}"
echo
printf '%s' "$_SN"
printf -v s '%*s' $(( (80 - 26) / 2 )) ''
read -rsp "${s}Press Enter to continue..."
echo
}
# ═══ SANDBOX ═══
SANDBOX_MODE=1 # ON by default
SANDBOX_PRISTINE="$DATA/sandbox.pristine"
SANDBOX_DIR="$DATA/sandbox"
_check_bwrap() {
command -v bwrap &>/dev/null || {
printf '%s\n' "${R}Error: bubblewrap (bwrap) not installed${N}" >&2
echo "Install: paru -S bubblewrap" >&2
SANDBOX_MODE=0
return 1
}
}
# shellcheck disable=SC2034 # locals used via _fpick nameref
_gen_log() {
local f="$1" lines="${2:-50}"
local ips=("192.168.1.100" "10.0.0.5" "172.16.0.50" "192.168.8.1" "10.10.10.10")
local codes=("200" "200" "200" "200" "301" "304" "400" "403" "404" "500")
local methods=("GET" "GET" "GET" "POST" "PUT" "DELETE")
local paths=("/" "/index.html" "/api/users" "/api/data" "/login" "/static/style.css" "/favicon.ico" "/admin" "/search" "/api/v1/status")
local agents=("Mozilla/5.0" "curl/7.68" "wget" "Python-requests" "Go-http-client")
local now; printf -v now '%(%s)T' -1
local step=$((86400 / lines))
: > "$f"
for ((i=0; i<lines; i++)); do
_fpick ips; local ip=$REPLY; _fpick codes; local code=$REPLY
_fpick methods; local method=$REPLY; _fpick paths; local path=$REPLY
_fpick agents; local agent=$REPLY; local size=$((RANDOM % 50000 + 100))
local ts; printf -v ts '%(%d/%b/%Y:%H:%M:%S +0000)T' "$((now - (lines - i) * step + RANDOM % step))"
printf '%s - - [%s] "%s %s HTTP/1.1" %s %d "%s"\n' "$ip" "$ts" "$method" "$path" "$code" "$size" "$agent"
done >> "$f"
}
# shellcheck disable=SC2034 # locals used via _fpick nameref
_gen_app_log() {
local f="$1" lines="${2:-40}"
local levels=("INFO" "INFO" "INFO" "INFO" "WARN" "WARN" "ERROR" "DEBUG")
local msgs_info=("Request processed" "User logged in" "Cache hit" "Connection established" "Task completed" "Session started")
local msgs_warn=("Slow query detected" "Memory usage high" "Rate limit approaching" "Deprecated API used" "Retry attempt")
local msgs_error=("Connection refused" "Timeout exceeded" "Invalid request" "Permission denied" "Database error" "Null pointer")
local now; printf -v now '%(%s)T' -1
local step=$((86400 / lines))
: > "$f"
# Guarantee at least one of each level in first 4 lines
local forced=("INFO" "WARN" "ERROR" "DEBUG")
for ((i=0; i<lines; i++)); do
local level
if ((i < 4)); then level=${forced[$i]}; else _fpick levels; level=$REPLY; fi
local ts; printf -v ts '%(%Y-%m-%d %H:%M:%S)T' "$((now - (lines - i) * step + RANDOM % step))"
local msg
case "$level" in
INFO|DEBUG) _fpick msgs_info; msg=$REPLY;;
WARN) _fpick msgs_warn; msg=$REPLY;;
ERROR) _fpick msgs_error; msg=$REPLY;;
esac
printf '[%s] %s: %s\n' "$ts" "$level" "$msg"
done >> "$f"
}
# shellcheck disable=SC2034 # locals used via _fpick nameref
_gen_csv() {
local f="$1" rows="${2:-30}"
local names=("Alice" "Bob" "Charlie" "Diana" "Eve" "Frank" "Grace" "Henry" "Ivy" "Jack")
local domains=("example.com" "test.org" "demo.net" "sample.io")
local statuses=("active" "active" "active" "inactive" "pending")
local now; printf -v now '%(%s)T' -1
echo "id,name,email,status,timestamp" > "$f"
for ((i=1; i<=rows; i++)); do
_fpick names; local name=$REPLY; _fpick domains; local domain=$REPLY
_fpick statuses; local status=$REPLY
local ts; printf -v ts '%(%Y-%m-%d)T' "$((now - RANDOM % 2592000))"
printf '%d,%s,%s@%s,%s,%s\n' "$i" "$name" "${name,,}" "$domain" "$status" "$ts"
done >> "$f"
}
_gen_ini() {
printf '%s\n' '[server]' 'host = 0.0.0.0' 'port = 8080' 'workers = 4' 'timeout = 30' '' \
'[database]' 'type = postgresql' 'host = localhost' 'port = 5432' 'name = appdb' 'user = admin' '' \
'[logging]' 'level = info' 'file = /var/log/app.log' 'rotate = daily' '' \
'[cache]' 'enabled = true' 'ttl = 3600' 'backend = redis' > "$1"
}
_gen_yaml() {
printf '%s\n' 'server:' ' host: 0.0.0.0' ' port: 8080' ' workers: 4' ' ssl:' \
' enabled: false' ' cert: /etc/ssl/cert.pem' '' 'database:' ' primary:' \
' host: localhost' ' port: 5432' ' name: appdb' ' replica:' \
' host: replica.local' ' port: 5432' '' 'logging:' ' level: info' ' outputs:' \
' - stdout' ' - file:/var/log/app.log' '' 'features:' ' cache: true' \
' metrics: true' ' debug: false' > "$1"
}
_gen_txt() {
printf '%s\n' 'Meeting Notes - Project Review' '==============================' '' \
'Attendees: Alice, Bob, Charlie' '' 'Action Items:' '1. Update documentation' \
'2. Fix authentication bug' '3. Deploy to staging' '4. Review security patches' '' \
'Discussion Points:' '- Performance improvements needed' '- New feature requests from users' \
'- Timeline for next release' '' 'TODO: Schedule follow-up meeting' \
'TODO: Review pull requests' 'TODO: Update dependencies' '' 'Notes:' \
'The deployment went smoothly.' 'Minor issues with caching fixed.' \
'Need to monitor error rates.' '' 'Completed tasks:' '- Database migration' \
'- API versioning' '- Load testing' > "$1"
}
# shellcheck disable=SC2034 # locals used via _fpick nameref
_gen_users_csv() {
local f="$1" rows="${2:-20}"
local fnames=("john" "jane" "mike" "sarah" "tom" "lisa" "david" "emma" "alex" "olivia")
local lnames=("smith" "jones" "wilson" "brown" "taylor" "davis" "miller" "garcia" "martinez" "lee")
local roles=("admin" "user" "user" "user" "moderator" "guest")
local now; printf -v now '%(%s)T' -1
echo "username,fullname,role,created,logins" > "$f"
for ((i=1; i<=rows; i++)); do
_fpick fnames; local fn=$REPLY; _fpick lnames; local ln=$REPLY
_fpick roles; local role=$REPLY; local logins=$((RANDOM % 500))
local ts; printf -v ts '%(%Y-%m-%d)T' "$((now - RANDOM % 31536000))"
printf '%s_%s,%s %s,%s,%s,%d\n' "$fn" "$ln" "${fn^}" "${ln^}" "$role" "$ts" "$logins"
done >> "$f"
}
_gen_json() {
printf '%s\n' '{' ' "version": "1.0.0",' ' "data": [' \
' {"id": 1, "name": "Item One", "status": "active", "count": 42},' \
' {"id": 2, "name": "Item Two", "status": "pending", "count": 17},' \
' {"id": 3, "name": "Item Three", "status": "active", "count": 89},' \
' {"id": 4, "name": "Item Four", "status": "inactive", "count": 5}' \
' ],' ' "metadata": {' ' "total": 4,' ' "generated": "2024-01-15"' ' }' '}' > "$1"
}
_gen_sandbox_files() {
local dir="$1"
mkdir -p "$dir"/{src/test,logs,data,backup,temp,config,test}
_gen_log "$dir/server.log" 50
_gen_app_log "$dir/app.log" 40
_gen_csv "$dir/data.csv" 30
_gen_users_csv "$dir/users.csv" 20
_gen_ini "$dir/config.ini"
_gen_yaml "$dir/settings.yaml"
_gen_txt "$dir/notes.txt"
printf '%s\n' 'Task List' '=========' '' '[ ] Review PR #123' '[ ] Update API docs' \
'[ ] Fix login timeout' '[x] Deploy v2.1.0' '[x] Merge feature branch' \
'[ ] Refactor user service' '[ ] Add unit tests' '[ ] Setup CI pipeline' > "$dir/todo.txt"
_gen_json "$dir/data/export.json"
_gen_log "$dir/logs/access.log" 30
_gen_app_log "$dir/logs/error.log" 20
printf '%s\n' '#!/bin/bash' 'echo "Backup started at $(date)"' 'echo "Backing up files..."' 'echo "Backup complete!"' > "$dir/backup.sh"
chmod +x "$dir/backup.sh"
printf '%s\n' '#!/bin/bash' 'echo "Deploying application..."' "echo \"Version: \$(cat VERSION 2>/dev/null || echo 'unknown')\"" 'echo "Deploy complete!"' > "$dir/deploy.sh"
chmod +x "$dir/deploy.sh"
printf '%s\n' '#!/usr/bin/env python3' '"""Main application module."""' '' \
'def main():' ' print("Hello, World!")' ' return 0' '' \
'if __name__ == "__main__":' ' main()' > "$dir/main.py"
printf '%s\n' '"""Utility functions."""' '' 'def helper(x):' ' return x * 2' '' \
'def process(data):' ' return [helper(item) for item in data]' > "$dir/src/utils.py"
printf '%s\n' '"""Tests for utils module."""' 'import unittest' '' \
'class TestUtils(unittest.TestCase):' ' def test_helper(self):' \
' self.assertEqual(helper(2), 4)' '' 'if __name__ == "__main__":' \
' unittest.main()' > "$dir/src/test/test_utils.py"
}
_sandbox_init() {
((SANDBOX_MODE)) || return 0
_check_bwrap || return 1
# Check available disk space (need at least 10MB)
local _avail; _avail=$(df -k "${DATA%/*}" 2>/dev/null | awk 'NR==2{print $4}')
if [[ -n "$_avail" ]] && ((_avail < 10240)); then
printf '%s\n' "${Y}Warning: low disk space, sandbox disabled${N}" >&2
SANDBOX_MODE=0; return 1
fi
if [[ ! -d "$SANDBOX_PRISTINE" ]]; then
_gen_sandbox_files "$SANDBOX_PRISTINE"
fi
_sandbox_reset
}
_sandbox_reset() {
((SANDBOX_MODE)) || return 0
[[ -d "$SANDBOX_PRISTINE" ]] || return 1
chmod -R u+rwX "$SANDBOX_DIR" 2>/dev/null # unlock any restricted dirs (e.g. scenario 4)
local _sb_tmp; _sb_tmp=$(mktemp -d "${SANDBOX_DIR}.XXXXXX") || { SANDBOX_MODE=0; return 1; }
rm -rf "$_sb_tmp"
if ! cp -a "$SANDBOX_PRISTINE" "$_sb_tmp"; then
rm -rf "$_sb_tmp"
printf '%s\n' "${Y}Warning: sandbox reset failed, disabling${N}" >&2
SANDBOX_MODE=0; return 1
fi
rm -rf "$SANDBOX_DIR"
mv "$_sb_tmp" "$SANDBOX_DIR"
}
_sandbox_exec() {
local cmd="$1" timeout_sec="${2:-$SANDBOX_TIMEOUT}"
((SANDBOX_MODE)) || return 1
local -a bwrap_args=(
--ro-bind /usr /usr
--ro-bind /bin /bin
--ro-bind /etc/passwd /etc/passwd
--ro-bind /etc/group /etc/group
--bind "$SANDBOX_DIR" /sandbox
--chdir /sandbox
--setenv HOME /sandbox
--setenv USER sandbox
--unshare-all
--die-with-parent
--new-session
--dev /dev
--tmpfs /tmp:size=64M
)
[[ -e /lib ]] && bwrap_args+=(--ro-bind /lib /lib)
[[ -e /lib64 ]] && bwrap_args+=(--ro-bind /lib64 /lib64)
local _rc
timeout "$timeout_sec" bwrap "${bwrap_args[@]}" /bin/bash -c 'eval "$1"' -- "$cmd"; _rc=$?
((_rc >= 125)) && printf '%s\n' "${R}sandbox error (exit ${_rc})${N}" >&2
return "$_rc"
}
_SANDBOX_DESTRUCTIVE=0
_is_destructive() {
local cmd="$1"
[[ "$cmd" =~ (^|[[:space:];&|])(rm|mv|cp|dd|truncate|shred|unlink|tee|chmod|chown|mkfs|fdisk|parted|install|split|patch|ln|mkdir|touch)[[:space:]] ]] && return 0
[[ "$cmd" =~ [^0-9](\>\>|\>[^>])|^(\>\>|\>[^>]) ]] && return 0
[[ "$cmd" =~ sed[[:space:]]+(-i|--in-place) ]] && return 0
[[ "$cmd" =~ :[[:space:]]*\> ]] && return 0
[[ "$cmd" =~ find[[:space:]].*-delete ]] && return 0
[[ "$cmd" =~ rsync[[:space:]].*--delete|xargs[[:space:]].*(rm|mv)|find[[:space:]].*-exec[[:space:]].*(rm|mv) ]] && return 0
[[ "$cmd" =~ (^|[[:space:];&|])(eval|exec|source)[[:space:]] ]] && return 0
[[ "$cmd" =~ \&\> ]] && return 0
return 1
}
# Returns 0 if output matches expected
# Formats:
# exact text - exact match
# ~regex - regex match
# @N - line count match
# * - any output (just check command ran)
_sandbox_check_output() {
local actual="$1" expected="$2"
[[ -z "$expected" ]] && return 1
case "$expected" in
\~*) # Regex match
local pattern="${expected:1}"
[[ "$actual" =~ $pattern ]] && return 0
;;
@*) # Line count match
local count="${expected:1}"
local lines=0
if [[ -n "$actual" ]]; then
actual="${actual%$'\n'}"
local _lc="${actual//[!$'\n']/}"; lines=$(( ${#_lc} + 1 ))
fi
((lines == count)) && return 0
;;
\*) # Any output
[[ -n "$actual" ]] && return 0
;;
*) # Exact match (trimmed)
_trim "$actual"; actual=$REPLY
_trim "$expected"; expected=$REPLY
[[ "$actual" == "$expected" ]] && return 0
;;
esac
return 1
}
# Formats: exists:F, !exists:F, contains:F:S, !contains:F:S, lines:F:N, line1:F:T, perm:F:M
_sandbox_check_state() {
local checks="$1" checks_arr=()
# Split on commas only when followed by a check keyword (avoids breaking values with commas)
local _rem="$checks" _seg
while [[ -n "$_rem" ]]; do
if [[ "$_rem" =~ ^((!?(exists|contains|lines|line1|perm)):[^,]*),((!?(exists|contains|lines|line1|perm)):) ]]; then
_seg="${BASH_REMATCH[1]}"
_rem="${_rem:${#_seg}+1}"
else _seg="$_rem"; _rem=""; fi
checks_arr+=("$_seg")
done
for check in "${checks_arr[@]}"; do
case "$check" in
exists:*|!exists:*)
local _neg=0; [[ "$check" == !* ]] && { _neg=1; check="${check:1}"; }
local file="${check#exists:}"
((_neg)) && { [[ ! -e "$SANDBOX_DIR/$file" ]] || return 1; } || { [[ -e "$SANDBOX_DIR/$file" ]] || return 1; }
;;
contains:*:*|!contains:*:*)
local _neg=0; [[ "$check" == !* ]] && { _neg=1; check="${check:1}"; }
local rest="${check#contains:}"; local file="${rest%%:*}" pattern="${rest#*:}"
if ((_neg)); then grep -qF "$pattern" -- "$SANDBOX_DIR/$file" 2>/dev/null && return 1
else grep -qF "$pattern" -- "$SANDBOX_DIR/$file" 2>/dev/null || return 1; fi
;;
lines:*:*)
local rest="${check#lines:}"; local file="${rest%%:*}" count="${rest#*:}"
[[ -f "$SANDBOX_DIR/$file" ]] || { ((count == 0)) && continue || return 1; }
local actual; actual=$(wc -l < "$SANDBOX_DIR/$file" 2>/dev/null)
((actual == count)) || return 1
;;
line1:*:*)
local rest="${check#line1:}"; local file="${rest%%:*}" text="${rest#*:}"
local first; read -r first < "$SANDBOX_DIR/$file" 2>/dev/null || return 1
[[ "$first" == *"$text"* ]] || return 1
;;
perm:*:*|!perm:*:*)
local _neg=0; [[ "$check" == !* ]] && { _neg=1; check="${check:1}"; }
local rest="${check#perm:}"; local file="${rest%%:*}" perms="${rest#*:}"
local path="$SANDBOX_DIR/$file"
[[ ! -e "$path" ]] && { ((_neg)) && continue || return 1; }
local _p; for ((_p=0; _p<${#perms}; _p++)); do
test -"${perms:$_p:1}" "$path"; local _prc=$?; ((_neg ? !_prc : _prc)) && return 1; done
;;
esac
done
return 0
}
# ═══ SESSION ═══
_stty_saved=$(stty -g 2>/dev/null) || _stty_saved=""
_interactive=0 _CLEANING=0
_cleanup() {
((_CLEANING)) && return; _CLEANING=1
_sflush_if_dirty 2>/dev/null
[[ -n "$_stty_saved" ]] && stty "$_stty_saved" 2>/dev/null
((_interactive)) || return 0
rm -rf "$DATA/sandbox.snap" "$DATA/sandbox.snap.perms" 2>/dev/null
printf '\e[?25h'
if ((_S_ANSWERED > 0)); then
local _ts; printf -v _ts '%(%s)T' -1; local el=$((_ts - _S_START))
printf '\n%s %s/%s (%d%%) streak %s %dm%ds\n' \
"${U}Session${N}" "${G}${_S_CORRECT}${N}" "${_S_ANSWERED}" \
$((_S_CORRECT*100/_S_ANSWERED)) "${W}${_S_BEST_STREAK}${N}" \
$((el/60)) $((el%60))
else echo; fi
}
trap '_cleanup; exit 0' INT TERM HUP EXIT
((EUID == 0)) && PROMPT_CHAR='# ' || PROMPT_CHAR='$ '
PROMPT_CHAR="${CMD_PROMPT:-$PROMPT_CHAR}"
# ═══ FLOPPY DISKS ═══
_DISK_TOTAL=8
declare -A _DISK_DESC=(
[sudorm]="Answer sudo rm -rf /"
[forkbomb]="Answer a fork bomb"
[rtfm]="Answer just 'man' with no arguments"
[streak10]="Get a 10+ answer streak"
[nightowl]="Play between midnight and 4am"
[flawless]="Beat a boss without a single mistake"
[vimquit]="Try to :q! or :wq out of the game"
[completionist]="Complete all scenarios"
)
_disk_found() {
local name=$1
[[ ",$DISKS_FOUND," == *",$name,"* ]] && return
[[ -n "$DISKS_FOUND" ]] && DISKS_FOUND="$DISKS_FOUND,$name" || DISKS_FOUND="$name"
_save_profile
_csv_count "$DISKS_FOUND"; local disk_count=$REPLY
printf '\n%s\n' "${Y}${B}▄▄▄▄▄▄▄${N}"
printf '%s\n' "${Y}${B}█ ░░░ █${N} ${Y}${B}FLOPPY DISK: ${name}${N}"
printf '%s\n' "${Y}${B}█▄▄▄▄▄█${N} ${D}${_DISK_DESC[$name]:-}${N}"
printf '%s\n' "${Y}${B}███████${N} ${D}${disk_count}/${_DISK_TOTAL} collected${N}"
printf '%s\n\n' "${Y}${B}▀▀▀▀▀▀▀${N}"
sleep 5
}
_disk_check() {
local trigger=$1 ctx=${2:-}
case "$trigger" in
wrong)
[[ "$ctx" == "sudo rm -rf /"* ]] && _disk_found sudorm
[[ "$ctx" == *":()"*":|:"* ]] && _disk_found forkbomb
[[ "$ctx" == "man" ]] && _disk_found rtfm
[[ "$ctx" == ":q"* || "$ctx" == ":wq"* ]] && _disk_found vimquit
;;
streak) ((_S_STREAK >= 10)) && _disk_found streak10;;
nightowl) local h; printf -v h '%(%H)T' -1; ((h < 4)) && _disk_found nightowl;;
flawless) _disk_found flawless;;
completionist)
local i done=1
for ((i=1; i<=SC_TOTAL; i++)); do
[[ ",$SC_DONE," == *",$i,"* ]] || { done=0; break; }
done
((done)) && _disk_found completionist
;;
esac
}
# ═══ VARIABLE POOLS ═══
# shellcheck disable=SC2034 # pool arrays accessed via nameref (_fpick/_ctx)
{
SANDBOX_LOGS=(server.log app.log logs/access.log logs/error.log)
SANDBOX_TXTS=(notes.txt todo.txt)
SANDBOX_CSVS=(data.csv users.csv)
SANDBOX_CFGS=(config.ini settings.yaml)
SANDBOX_SCRIPTS=(backup.sh deploy.sh)
LOGS=(server.log app.log system.log auth.log access.log error.log debug.log nginx.log apache.log syslog
messages kern.log daemon.log mail.log secure dpkg.log pacman.log audit.log boot.log faillog lastlog
wtmp btmp cron.log yum.log dmesg)
CONFIGS=(config.ini settings.conf app.yaml server.conf nginx.conf httpd.conf my.cnf pg.conf
redis.conf sshd_config sudoers fstab hosts resolv.conf profile bashrc vimrc tmux.conf gitconfig
docker-compose.yml)
SCRIPTS=(backup.sh deploy.sh build.sh test.sh cleanup.sh migrate.sh sync.sh init.sh
setup.sh install.sh update.sh monitor.sh healthcheck.sh rotate.sh fetch.sh push.sh start.sh stop.sh
restart.sh validate.sh)
TXTS=(data.txt notes.txt output.txt report.txt dump.txt results.txt temp.txt
readme.txt todo.txt changelog.txt manifest.txt inventory.txt urls.txt hosts.txt targets.txt)
CSVS=(data.csv users.csv logs.csv export.csv report.csv stats.csv
metrics.csv transactions.csv inventory.csv orders.csv customers.csv events.csv)
BINS=(binary.exe program.elf app.bin firmware.bin malware.exe sample.bin
payload.dll trojan.exe rootkit.so agent.bin dropper.exe beacon.bin)
IMGS=(disk.img backup.img system.img evidence.img
memory.dmp vmcore.dmp hiberfil.sys pagefile.sys snapshot.raw forensic.dd)
PCAPS=(capture.pcap traffic.pcap network.pcap dump.pcap
scan.pcap attack.pcap session.pcap handshake.pcap dns.pcap http.pcap)
HASHES=(hash.txt hashes.txt md5.txt sha256.txt passwd.txt shadow.txt
ntlm.txt cracked.txt potfile.txt wordlist.hash lm.txt bcrypt.txt)
WORDLISTS=(wordlist.txt rockyou.txt passwords.txt dict.txt
common.txt custom.txt leaked.txt company.txt names.txt cities.txt hybrid.txt rules.txt)
ARCHIVES=(backup.tar.gz archive.tar.gz files.tgz data.tar.bz2 export.zip
snapshot.tar.xz bundle.tar release.tar.gz package.tar.gz source.tar.bz2 logs.tar.gz dump.tar.zst)
PHOTOS=(photo.jpg image.png screenshot.png evidence.jpg document.pdf
scan.tiff capture.bmp diagram.svg icon.ico avatar.webp thumbnail.gif render.raw)
DIRS=(src lib bin tmp logs data config backup output cache
build dist vendor node_modules target public static assets uploads downloads)
EXTS=(py js ts go rs c cpp java rb sh log txt conf yaml json
toml md html css sql xml csv ini env php pl lua zig swift)
SEARCH_TERMS=(error warning panic fatal timeout exception failed invalid denied refused connection
unauthorized forbidden crash segfault nullptr overflow leak abort reject throttle blocked suspend
corrupt missing expired revoked)
USERNAMES=(admin root user guest test deploy www-data nginx postgres mysql
git jenkins ansible prometheus grafana redis mongodb elastic backup operator service)
IPS=(10.0.0.1 10.0.0.5 10.10.10.10 192.168.1.1 192.168.1.100 172.16.0.1 10.0.0.50
192.168.0.1 172.16.1.1 10.10.10.50 192.168.100.1 10.20.30.40 172.31.0.1 192.168.50.50 10.0.1.1)
SUBNETS=(10.0.0.0/24 192.168.1.0/24 172.16.0.0/16 10.10.10.0/24
192.168.0.0/24 172.17.0.0/16 10.20.0.0/16 192.168.100.0/24 10.0.0.0/8 172.31.0.0/16)
PORTS=(22 80 443 8080 3000 5000 8000 3306 5432 6379 27017
21 23 25 53 110 143 389 445 993 995 1433 1521 5900 8443)
MACS=(AA:BB:CC:DD:EE:FF 11:22:33:44:55:66 DE:AD:BE:EF:CA:FE
00:11:22:33:44:55 A1:B2:C3:D4:E5:F6 12:34:56:78:9A:BC FE:DC:BA:98:76:54
CA:FE:BA:BE:00:01 01:23:45:67:89:AB AB:CD:EF:12:34:56)
URLS=(http://10.0.0.1 http://192.168.1.100 http://target.local http://10.10.10.10
http://192.168.0.1 http://172.16.1.1 http://dev.local http://staging.local
http://app.local http://api.local http://admin.local http://internal.local)
FIELDS=(name id status email timestamp user_id created_at value count type
updated_at deleted_at version priority level source target duration size)
JSON_KEYS=(data items results users records entries
events messages logs errors nodes objects resources payload)
REPLACE_OLD=(foo old debug localhost http TODO FIXME tmp
dev staging test alpha beta v1 deprecated warn error secret)
REPLACE_NEW=(bar new prod 10.0.0.1 https DONE RESOLVED cache
main production live release stable v2 current info success masked)
SIZES=(1k 5k 10k 100k 500k 10M 50M 100M 500M 1G 2G 5G 10G)
HASHCAT_MODES=(0 100 1400 1800 3200 1000 5600 13100 18200 22000 500 1700 2500 5500 11300)
HASH_NAMES=(MD5 SHA1 SHA256 sha512crypt bcrypt NTLM NetNTLMv2 Kerberoast-TGS ASREPRoast WPA-PBKDF2 md5crypt SHA-512 WPA NetNTLMv1 Bitcoin)
INTERFACES=(eth0 wlan0 ens33 enp0s3 ens192 enp0s25 wlp2s0 docker0 br0 virbr0)
WIFI_INTERFACES=(wlan0 wlp2s0 wlp3s0 wlan1)
REMOTE_HOSTS=(server backup-host db-server web-server jump-host bastion
prod-01 staging-01 dev-01 ci-runner build-server cache-01 redis-01 postgres-01 monitor)
PROCS=(nginx apache2 mysqld postgres redis-server mongod node python3 java ruby
docker containerd systemd sshd cron cups NetworkManager pulseaudio pipewire)
SERVICES=(nginx apache2 mysql postgresql redis docker containerd sshd
NetworkManager bluetooth cups cronie fail2ban ufw firewalld syncthing)
BRANCHES=(main master develop feature/auth feature/api fix/login hotfix/security release/v2)
}
# ═══ HELPERS ═══
_fpick() { local -n _a=$1; ((${#_a[@]})) || { REPLY=""; return 1; }; REPLY="${_a[$((RANDOM % ${#_a[@]}))]}"; }
_fphrase() { local -a _a=("$@"); REPLY="${_a[$((RANDOM % ${#_a[@]}))]}"; }
_frnum() { REPLY=$(($1 + RANDOM % ($2 - $1 + 1))); }
# Fisher-Yates in-place shuffle via nameref (replaces shuf fork)
# shellcheck disable=SC2178 # nameref pattern — _a is always an array
_fshuffle() { local -n _a=$1; local i j t; for ((i=${#_a[@]}-1; i>0; i--)); do j=$((RANDOM % (i+1))); t="${_a[$i]}"; _a[$i]="${_a[$j]}"; _a[$j]="$t"; done; }
# shellcheck disable=SC2178
_fshuffle_n() { _fshuffle "$1"; local -n _a=$1; (($2 < ${#_a[@]})) && _a=("${_a[@]:0:$2}"); }
_pause() { read -rsp "${D}Press Enter to ${1:-continue}...${N}"; }
_QV='log cfg cfg2 txt txt2 csv dir dir2 ext ext2 script archive bin img pcap hash photo wordlist ip subnet port port2 mac url user host term term2 term3 field key old new size iface proc svc branch n n1 n2 n3 col'
# shellcheck disable=SC2034 # vars consumed by caller's eval scope
_ctx() {
_fpick LOGS; log=$REPLY; _fpick CONFIGS; cfg=$REPLY; _fpick CONFIGS; cfg2=$REPLY
_fpick TXTS; txt=$REPLY; _fpick TXTS; txt2=$REPLY; _fpick CSVS; csv=$REPLY
_fpick DIRS; dir=$REPLY; _fpick DIRS; dir2=$REPLY; _fpick EXTS; ext=$REPLY; _fpick EXTS; ext2=$REPLY
_fpick SCRIPTS; script=$REPLY; _fpick ARCHIVES; archive=$REPLY; _fpick BINS; bin=$REPLY
_fpick IMGS; img=$REPLY; _fpick PCAPS; pcap=$REPLY; _fpick HASHES; hash=$REPLY; _fpick PHOTOS; photo=$REPLY
_fpick WORDLISTS; wordlist=$REPLY; _fpick IPS; ip=$REPLY; _fpick SUBNETS; subnet=$REPLY
_fpick PORTS; port=$REPLY; _fpick PORTS; port2=$REPLY; _fpick MACS; mac=$REPLY
_fpick URLS; url=$REPLY; _fpick USERNAMES; user=$REPLY; _fpick REMOTE_HOSTS; host=$REPLY
_fpick SEARCH_TERMS; term=$REPLY; _fpick SEARCH_TERMS; term2=$REPLY; _fpick SEARCH_TERMS; term3=$REPLY
_fpick FIELDS; field=$REPLY; _fpick JSON_KEYS; key=$REPLY
_fpick REPLACE_OLD; old=$REPLY; _fpick REPLACE_NEW; new=$REPLY; _fpick SIZES; size=$REPLY
_fpick INTERFACES; iface=$REPLY; _fpick PROCS; proc=$REPLY; _fpick SERVICES; svc=$REPLY
_fpick BRANCHES; branch=$REPLY
_frnum 1 20; n1=$REPLY; _frnum 5 50; n2=$REPLY; _frnum 2 10; n3=$REPLY; _frnum 1 5; col=$REPLY; n=$n1
}
# ═══ MANPAGES & EXP ═══
# Each entry: header\nbody lines (already formatted with escape codes)
declare -A MANPAGE=(
[cd]=$'change directory\n \e[32m.\e[0m current directory \e[32m..\e[0m parent directory\n \e[32m~\e[0m home directory \e[32m-\e[0m previous directory\n \e[32m../..\e[0m up two levels'
[echo]=$'print text to stdout\n \e[36m-n\e[0m no trailing newline \e[36m-e\e[0m enable escapes (\\n \\t)\n \e[33m"$VAR"\e[0m expands variables \e[33m\x27text\x27\e[0m literal (no expansion)\n \e[2mecho hello echo "$HOME" echo -n "no newline" echo -e "line1\\nline2"\e[0m'
[pwd]=$'print working directory\n shows absolute path of current directory\n \e[2mpwd # /home/user/projects\e[0m'
[touch]=$'create empty file or update timestamp\n \e[36m-t\e[0m \e[33mSTAMP\e[0m set specific time\n \e[2mtouch newfile touch -t 202301010000 file\e[0m'
[mv]=$'move or rename files\n \e[36m-i\e[0m confirm before overwrite\n \e[36m-n\e[0m never overwrite\n \e[2mmv old.txt new.txt mv file.txt dir/ mv *.log archive/\e[0m'
[rm]=$'delete files permanently\n \e[36m-r\e[0m recursive (dirs) \e[36m-f\e[0m force (no prompt)\n \e[36m-i\e[0m confirm each file\n \e[2mrm file.txt rm -r dir/ rm -rf dir/ rm -i *.log\e[0m'
[ln]=$'create links\n \e[36m-s\e[0m symbolic (soft) link \e[2m(without -s: hard link)\e[0m\n \e[35mhard\e[0m same inode, same disk \e[35msoft\e[0m path pointer, cross-disk\n \e[2mln file hardlink ln -s target symlink ln -sf target link\e[0m'
[test]=$'evaluate conditional expression\n \e[36m-f\e[0m \e[33mFILE\e[0m is regular file \e[36m-d\e[0m \e[33mFILE\e[0m is directory\n \e[36m-e\e[0m \e[33mFILE\e[0m exists \e[36m-r\e[0m \e[33mFILE\e[0m is readable\n \e[36m-z\e[0m \e[33mSTR\e[0m string is empty \e[36m-n\e[0m \e[33mSTR\e[0m string not empty\n \e[36m-eq\e[0m equal (numeric) \e[36m-lt\e[0m less than\n \e[36m-gt\e[0m greater than \e[36m-ne\e[0m not equal\n \e[2m[[ -f file ]] && echo yes [[ -z "$var" ]] [[ $a -eq $b ]]\e[0m'
[ls]=$'list directory contents\n \e[36m-l\e[0m details (permissions, owner, size, date)\n \e[36m-a\e[0m show hidden \e[36m-h\e[0m human sizes\n \e[36m-S\e[0m sort by size \e[36m-t\e[0m sort by time\n \e[36m-d\e[0m dirs themselves \e[36m-R\e[0m recursive\n \e[36m-1\e[0m one per line\n \e[2mls -la ls -lh ls -lS ls -lt ls -d */\e[0m'
[cat]=$'concatenate and print files\n \e[36m-n\e[0m number all lines\n \e[35mvs less\e[0m cat dumps everything at once (good for short files, piping)\n less lets you scroll (good for long files)\n \e[2mcat file.txt cat -n file.txt cat a.txt b.txt > merged.txt\e[0m'
[head]=$'output first part of files\n \e[36m-n\e[0m \e[33mN\e[0m first \e[33mN\e[0m lines \e[2m(or head -N)\e[0m\n \e[2mhead file.txt head -n 5 file.txt head -20 file.txt\e[0m'
[tail]=$'output last part of files\n \e[36m-n\e[0m \e[33mN\e[0m last \e[33mN\e[0m lines \e[36m-f\e[0m follow (live updates)\n \e[2mtail file.txt tail -n 20 file.txt tail -f /var/log/syslog\e[0m'
[less]=$'pager (scroll through files)\n \e[36m-N\e[0m show line numbers\n \e[2mless file.txt less -N file.txt\e[0m'
[cp]=$'copy files\n \e[36m-r\e[0m recursive (dirs) \e[36m-a\e[0m archive (preserve all)\n \e[2mcp file.txt backup.txt cp -r dir/ newdir/ cp -a dir/ newdir/\e[0m'
[mkdir]=$'make directories\n \e[36m-p\e[0m create parents\n \e[2mmkdir newdir mkdir -p path/to/dir\e[0m'
[tree]=$'directory tree\n \e[36m-L\e[0m \e[33mN\e[0m depth limit\n \e[2mtree tree -L 2\e[0m'
[tee]=$'copy stdin to file AND stdout\n \e[36m-a\e[0m append (don\x27t overwrite)\n \e[35mvs >>\e[0m >> sends to file only (terminal goes dark)\n tee sends to file AND keeps output on screen\n \e[2mecho hello | tee file.txt ls | tee -a log.txt\e[0m'
[grep]=$'search text patterns\n \e[36m-r\e[0m recursive \e[36m-i\e[0m ignore case\n \e[36m-n\e[0m line numbers \e[36m-c\e[0m count matches\n \e[36m-l\e[0m files only \e[36m-v\e[0m invert match\n \e[36m-o\e[0m matched text only \e[36m-q\e[0m quiet (exit code)\n \e[36m-w\e[0m whole word \e[36m-x\e[0m whole line\n \e[36m-E\e[0m extended regex \e[2m(+?|)\e[0m \e[36m-P\e[0m perl regex \e[2m(\\d \\w lookahead)\e[0m\n \e[36m-F\e[0m literal string \e[36m-f\e[0m \e[33mFILE\e[0m patterns from file\n \e[36m-A\e[0m \e[33mN\e[0m lines after \e[36m-B\e[0m \e[33mN\e[0m lines before\n \e[36m-m\e[0m \e[33mN\e[0m max matches \e[36m-C\e[0m \e[33mN\e[0m context lines\n \e[36m--include\e[0m file pattern \e[36m--exclude-dir\e[0m skip directory\n \e[35mvs rg\e[0m grep is POSIX (everywhere). rg is faster, skips .gitignore,\n recurses by default. use grep for portability, rg for speed\n \e[2mgrep -rn TODO . grep -ri pattern file grep -oE \'[0-9]+\' log\e[0m'
[rg]=$'fast grep (ripgrep)\n \e[36m-i\e[0m ignore case \e[36m-v\e[0m invert match\n \e[36m-n\e[0m line numbers \e[36m-c\e[0m count matches\n \e[36m-l\e[0m files only \e[36m-o\e[0m matched text only\n \e[36m-q\e[0m quiet (exit code) \e[36m-w\e[0m whole word\n \e[36m-x\e[0m whole line \e[36m-F\e[0m literal string\n \e[36m-f\e[0m \e[33mFILE\e[0m patterns from file \e[36m-t\e[0m \e[33mTYPE\e[0m file type filter\n \e[36m-g\e[0m \e[33mGLOB\e[0m file glob filter \e[36m-m\e[0m \e[33mN\e[0m max matches\n \e[36m-A\e[0m \e[33mN\e[0m lines after \e[36m-B\e[0m \e[33mN\e[0m lines before\n \e[36m-C\e[0m \e[33mN\e[0m context lines\n \e[35mvs grep\e[0m rg is faster, recursive by default, skips .gitignore.\n grep is POSIX (works on any machine). use rg locally, grep in scripts\n \e[2mrg pattern rg -i todo rg -t py import rg -l error\e[0m'
[find]=$'search for files\n \e[36m-name\e[0m \e[33m\'X\'\e[0m filename \e[36m-iname\e[0m case-insensitive\n \e[36m-type\e[0m \e[2mf/d/l\e[0m file type \e[36m-size\e[0m \e[2m+10M\e[0m by size\n \e[36m-mtime\e[0m \e[2m-7\e[0m modified days \e[36m-mmin\e[0m \e[2m-60\e[0m modified mins\n \e[36m-amin\e[0m \e[2m-60\e[0m accessed mins \e[36m-empty\e[0m empty files\n \e[36m-executable\e[0m has exec bit \e[36m-perm\e[0m \e[2m-4000\e[0m by permission\n \e[36m-maxdepth\e[0m \e[33mN\e[0m limit depth \e[36m-path\e[0m \e[33m\'X\'\e[0m match path\n \e[36m-not\e[0m negate \e[36m-print0\e[0m null delimit\n \e[36m-exec\e[0m \e[33mCMD\e[0m \e[32m{} \\;\e[0m run per file \e[36m-exec\e[0m \e[33mCMD\e[0m \e[32m{} +\e[0m batch (faster)\n \e[36m-delete\e[0m remove\n \e[35mvs fd\e[0m find is POSIX (everywhere, complex predicates). fd is faster,\n uses regex, skips .gitignore. use find for scripts, fd for speed\n \e[2mfind . -name "*.log" find / -size +100M find . -type f -empty\e[0m'
[fd]=$'fast find\n \e[36m-e\e[0m \e[33mEXT\e[0m file extension \e[36m-t\e[0m \e[33mTYPE\e[0m f/d/l/x/e\n \e[36m-d\e[0m \e[33mN\e[0m max depth \e[36m-E\e[0m \e[33mPAT\e[0m exclude pattern\n \e[36m-H\e[0m include hidden \e[36m-i\e[0m ignore case\n \e[36m-S\e[0m \e[33mSIZE\e[0m by size (+/-N) \e[36m-l\e[0m long listing\n \e[36m-p\e[0m match full path \e[36m-g\e[0m glob pattern\n \e[36m-x\e[0m \e[33mCMD\e[0m execute per result\n \e[36m--changed-within\e[0m \e[33mT\e[0m \e[36m--changed-before\e[0m \e[33mT\e[0m\n \e[35mvs find\e[0m fd is faster, regex by default, skips .gitignore + hidden.\n find is POSIX (works anywhere), has -exec {} +, -delete, complex logic\n \e[2mfd pattern fd -e py fd -t d fd -S +10M fd -x rm {}\e[0m'
[sort]=$'sort lines of text\n \e[36m-n\e[0m numeric \e[36m-r\e[0m reverse\n \e[36m-u\e[0m unique \e[36m-k\e[0m \e[33mN\e[0m by column \e[33mN\e[0m\n \e[36m-t\e[0m \e[33mC\e[0m delimiter \e[36m-h\e[0m human numeric\n \e[36m-f\e[0m ignore case\n \e[2msort file.txt sort -n nums.txt sort -t, -k2 data.csv\e[0m'
[uniq]=$'filter repeated lines\n \e[36m-c\e[0m count occurrences \e[36m-d\e[0m only duplicates\n \e[36m-u\e[0m only unique \e[36m-D\e[0m all duplicates\n \e[35mvs sort -u\e[0m sort -u just deduplicates. uniq can COUNT (-c), show\n ONLY dupes (-d), or ONLY unique (-u). that\'s why both exist\n \e[2msort file | uniq sort file | uniq -c sort file | uniq -D\e[0m'
[cut]=$'extract columns/fields\n \e[36m-d\e[33mC\e[0m delimiter \e[36m-f\e[33mN\e[0m field \e[33mN\e[0m\n \e[36m-c\e[33mN\e[0m character \e[33mN\e[0m \e[36m--complement\e[0m invert\n \e[35mvs awk\e[0m cut is simpler/faster for grabbing columns.\n awk is a full language — use it when you need math, conditions, or logic\n \e[2mcut -d, -f1 data.csv cut -d: -f1 /etc/passwd cut -c1-10 file\e[0m'
[wc]=$'word, line, byte count\n \e[36m-l\e[0m lines \e[36m-w\e[0m words\n \e[36m-c\e[0m bytes \e[36m-m\e[0m characters\n \e[2mwc -l file.txt wc -w file.txt ls | wc -l\e[0m'
[tr]=$'translate characters\n \e[36m-d\e[0m delete chars \e[36m-s\e[0m squeeze consecutive dupes to one\n \e[32m<\e[0m \e[33mFILE\e[0m tr reads stdin — redirect from file\n \e[2mtr a-z A-Z < file tr -d \'\\n\' < file tr -s \' \' < file\e[0m'
[sed]=$'stream editor\n \e[31m\'s/old/new/\'\e[0m replace first \e[31m\'s/old/new/g\'\e[0m replace all\n \e[36m-i\e[0m edit in place \e[31m\'/pat/d\'\e[0m delete lines\n \e[36m-n\e[0m suppress output \e[36m-e\e[0m multi commands\n \e[36m-E\e[0m extended regex\n \e[35mvs awk\e[0m sed is best for search-and-replace and line filtering.\n awk is best for column-based processing and math\n \e[2msed \'s/foo/bar/g\' file sed -i \'/^#/d\' file sed -n \'5,10p\' file\e[0m'
[awk]=$'pattern scanning\n \e[31m\'{print $1}\'\e[0m first field \e[31m\'{print $NF}\'\e[0m last field\n \e[36m-F\e[0m\e[2m\',\'\e[0m delimiter \e[31mNR\e[0m line number\n \e[2mawk \'{print $1}\' file awk -F, \'{print $2}\' data.csv awk \'/error/\' log\e[0m'
[xargs]=$'build commands from stdin\n \e[36m-I\e[0m \e[32m{}\e[0m placeholder \e[36m-0\e[0m null delim\n \e[36m-n\e[0m \e[33mN\e[0m args per command \e[36m-P\e[0m \e[33mN\e[0m parallel\n \e[36m-p\e[0m confirm each \e[36m-t\e[0m trace (show cmd)\n \e[2mfind . -print0 | xargs -0 rm cat urls | xargs -P4 -I{} curl {}\e[0m'
[tar]=$'archive tool\n \e[36m-c\e[0m create \e[36m-x\e[0m extract \e[36m-t\e[0m list \e[36m-f\e[0m \e[33mFILE\e[0m\n \e[36m-z\e[0m gzip \e[36m-C\e[0m \e[33mDIR\e[0m extract to\n \e[2mtar czf archive.tar.gz dir/ tar xzf archive.tar.gz tar tf archive.tar.gz\e[0m'
[chmod]=$'change file permissions\n \e[32m+x\e[0m add execute \e[32m755\e[0m rwxr-xr-x\n \e[32m644\e[0m rw-r--r-- \e[32mg+w\e[0m group write\n \e[36m-R\e[0m recursive \e[36m--reference\e[0m=\e[33mFILE\e[0m copy perms\n \e[2mchmod +x script.sh chmod 755 script.sh chmod -R a+r dir/\e[0m'
[ps]=$'list processes\n \e[32maux\e[0m all users, detailed \e[36m--sort\e[0m=\e[33mFIELD\e[0m sort by field\n \e[2mps aux ps aux --sort=-%mem | head ps auxf\e[0m'
[du]=$'disk usage\n \e[36m-h\e[0m human readable \e[36m-s\e[0m summary only\n \e[2mdu -sh . du -sh */\e[0m'
[df]=$'disk free space\n \e[36m-h\e[0m human readable\n \e[2mdf -h df -h /home\e[0m'
[ssh]=$'secure shell\n \e[36m-L\e[0m \e[2mP:H:P\e[0m local forward \e[36m-R\e[0m \e[2mP:H:P\e[0m remote forward\n \e[36m-D\e[0m \e[33mPORT\e[0m SOCKS proxy \e[36m-J\e[0m \e[33mHOST\e[0m jump host\n \e[36m-N\e[0m no command \e[36m-f\e[0m background\n \e[36m-T\e[0m no tty\n \e[2mssh user@host ssh -L 8080:localhost:80 host ssh -D 1080 host\e[0m'
[curl]=$'HTTP client\n \e[36m-X\e[0m \e[33mMETHOD\e[0m GET/POST/PUT/DELETE \e[36m-H\e[0m \e[2m\'K: V\'\e[0m header\n \e[36m-d\e[0m \e[33mDATA\e[0m POST body \e[36m-o\e[0m \e[33mFILE\e[0m output file\n \e[36m-L\e[0m follow redirects \e[36m-s\e[0m silent\n \e[36m-i\e[0m show headers \e[36m-O\e[0m save original name\n \e[35mvs wget\e[0m curl is for APIs (headers, POST, methods).\n wget is for downloading files (auto-resume, recursive mirror)\n \e[2mcurl -s url curl -X POST -d "data" url curl -O url/file.tar.gz\e[0m'
[wget]=$'file downloader\n \e[36m-c\e[0m continue download \e[36m-m\e[0m mirror site\n \e[36m-k\e[0m convert links\n \e[35mvs curl\e[0m wget is for downloading files (auto-resume, recursive).\n curl is for API work (headers, POST, methods)\n \e[2mwget url wget -c url wget -mk url\e[0m'
[git]=$'version control\n \e[32madd\e[0m stage \e[32mcommit\e[0m save \e[32mpush\e[0m upload\n \e[32mpull\e[0m download \e[32mstatus\e[0m changes \e[32mdiff\e[0m compare\n \e[32mlog\e[0m history \e[32mbranch\e[0m branches \e[32mmerge\e[0m combine\n \e[32mrebase\e[0m replay \e[32mstash\e[0m shelve \e[32mreset\e[0m undo\n \e[2mgit add . git commit -m "msg" git log --oneline git stash\e[0m'
[ss]=$'socket statistics\n \e[36m-t\e[0m TCP \e[36m-u\e[0m UDP\n \e[36m-l\e[0m listening \e[36m-n\e[0m numeric ports\n \e[36m-p\e[0m show process \e[36m-a\e[0m all sockets\n \e[35mvs netstat\e[0m ss is faster and the modern replacement.\n netstat is deprecated but still on older systems\n \e[2mss -tlnp ss -tunap ss -t state established\e[0m'
[systemctl]=$'systemd control\n \e[32mstart\e[0m start service \e[32mstop\e[0m stop service\n \e[32menable\e[0m start on boot \e[32mdisable\e[0m no auto-start\n \e[32mstatus\e[0m show status \e[32mrestart\e[0m restart service\n \e[32mreload\e[0m reload config \e[32mmask\e[0m prevent starting\n \e[32mdaemon-reload\e[0m reload unit files \e[36m--failed\e[0m show failures\n \e[2msystemctl status nginx systemctl enable sshd systemctl --failed\e[0m'
[journalctl]=$'systemd logs\n \e[36m-u\e[0m \e[33mUNIT\e[0m service logs \e[36m-f\e[0m follow live\n \e[36m-n\e[0m \e[33mN\e[0m last \e[33mN\e[0m lines \e[36m-b\e[0m current boot\n \e[36m-p\e[0m \e[33mLEVEL\e[0m priority (err/warn) \e[36m-k\e[0m kernel messages\n \e[36m-o\e[0m \e[33mFMT\e[0m output format \e[36m--since\e[0m time filter\n \e[36m--disk-usage\e[0m journal size \e[36m--vacuum-size\e[0m=\e[33mN\e[0m shrink to\n \e[36m--no-pager\e[0m no less/more\n \e[35mvs dmesg\e[0m journalctl indexes all system logs (structured, filterable).\n dmesg is kernel ring buffer only\n \e[2mjournalctl -u nginx -f journalctl -b -p err journalctl --disk-usage\e[0m'
[crontab]=$'scheduled tasks\n \e[36m-e\e[0m edit crontab \e[36m-l\e[0m list jobs\n \e[36m-r\e[0m remove all jobs\n \e[2mcrontab -e crontab -l sudo crontab -e\e[0m'
[tmux]=$'terminal multiplexer\n \e[32mnew\e[0m \e[36m-s\e[0m \e[33mNAME\e[0m new session \e[32mattach\e[0m \e[36m-t\e[0m \e[33mNAME\e[0m reattach\n \e[32mls\e[0m list sessions \e[32mkill-session\e[0m \e[36m-t\e[0m \e[33mNAME\e[0m\n \e[2mtmux new -s work tmux attach -t work tmux ls\e[0m'
[nmap]=$'network scanner\n \e[36m-sS\e[0m SYN (half-open) \e[36m-sT\e[0m TCP connect\n \e[36m-sU\e[0m UDP scan \e[36m-sV\e[0m version detect\n \e[36m-sC\e[0m default scripts \e[36m-O\e[0m OS detection\n \e[36m-A\e[0m aggressive (all) \e[36m-F\e[0m fast (top 100)\n \e[36m-p\e[0m \e[33mPORTS\e[0m specific ports \e[36m-p-\e[0m all 65535 ports\n \e[36m-Pn\e[0m skip discovery \e[36m-sn\e[0m ping sweep\n \e[36m-sL\e[0m list targets \e[36m-PR\e[0m ARP scan\n \e[36m-oA\e[0m \e[33mPFX\e[0m all output formats \e[36m-oG\e[0m \e[33mFILE\e[0m greppable output\n \e[36m-T1\e[0m slow (evasion) \e[36m-T4\e[0m fast timing\n \e[36m--top-ports\e[0m \e[33mN\e[0m top N ports \e[36m--script\e[0m \e[33mNAME\e[0m NSE script\n \e[2mnmap -sV -sC host nmap -A -p- host nmap -sn 192.168.1.0/24\e[0m'
[hydra]=$'network login cracker\n \e[36m-l\e[0m \e[33mUSER\e[0m single username \e[36m-L\e[0m \e[33mFILE\e[0m username list\n \e[36m-P\e[0m \e[33mFILE\e[0m password list \e[36m-t\e[0m \e[33mN\e[0m threads per target\n \e[2mhydra -l admin -P pass.txt ssh://192.168.1.1\e[0m'
[hashcat]=$'GPU hash cracker\n \e[36m-m\e[0m \e[33mTYPE\e[0m hash type \e[2m(0=MD5 100=SHA1 1000=NTLM 1800=sha512crypt)\e[0m\n \e[36m-a\e[0m \e[33mMODE\e[0m 0=dict 3=brute \e[36m--show\e[0m show cracked\n \e[36m-r\e[0m \e[33mFILE\e[0m rules file \e[36m--restore\e[0m resume session\n \e[35mvs john\e[0m hashcat uses GPU (fast for large lists).\n john is CPU-based, better auto-format detection\n \e[2mhashcat -m 0 hash.txt wordlist.txt hashcat -m 1000 -a 3 hash ?a?a?a?a\e[0m'
[john]=$'password cracker\n \e[36m--wordlist\e[0m=\e[33mFILE\e[0m dictionary attack\n \e[36m--show\e[0m display cracked \e[36m--list\e[0m=formats list types\n \e[35mvs hashcat\e[0m john is CPU-based with smart auto-format detection.\n hashcat uses GPU for massive throughput\n \e[2mjohn --wordlist=rockyou.txt hash.txt john --show hash.txt\e[0m'
[tshark]=$'terminal packet analyzer\n \e[36m-i\e[0m \e[33mIFACE\e[0m capture live \e[36m-r\e[0m \e[33mFILE\e[0m read pcap\n \e[36m-w\e[0m \e[33mFILE\e[0m write pcap \e[36m-c\e[0m \e[33mN\e[0m capture N packets\n \e[36m-Y\e[0m \e[33mFILTER\e[0m display filter \e[36m-T\e[0m fields output format\n \e[36m-e\e[0m \e[33mFIELD\e[0m extract field \e[36m-q\e[0m quiet (stats only)\n \e[36m-z\e[0m \e[33mSTAT\e[0m statistics\n \e[35mvs tcpdump\e[0m tshark has deep protocol dissection and display filters.\n tcpdump is lightweight and available everywhere\n \e[2mtshark -i eth0 -w cap.pcap tshark -r cap.pcap -Y http tshark -q -z conv,tcp\e[0m'
[tcpdump]=$'packet capture\n \e[36m-i\e[0m \e[33mIFACE\e[0m capture interface \e[36m-w\e[0m \e[33mFILE\e[0m write to pcap\n \e[36m-r\e[0m \e[33mFILE\e[0m read from pcap \e[36m-c\e[0m \e[33mN\e[0m capture N packets\n \e[36m-n\e[0m no DNS resolution \e[36m-vvv\e[0m max verbosity\n \e[35mvs tshark\e[0m tcpdump is lightweight, CLI-first, everywhere.\n tshark has deep protocol dissection and display filters\n \e[2mtcpdump -i eth0 -w cap.pcap tcpdump -r cap.pcap tcpdump -i eth0 -c 100\e[0m'
[nc]=$'network tool (netcat)\n \e[36m-l\e[0m listen mode \e[36m-v\e[0m verbose\n \e[36m-p\e[0m \e[33mPORT\e[0m local port \e[36m-z\e[0m scan (no data)\n \e[36m-n\e[0m no DNS resolution\n \e[35mvs ncat\e[0m nc (netcat) is basic but everywhere.\n ncat (from nmap) adds SSL, proxying, -e exec\n \e[2mnc -lvnp 4444 nc -zv host 1-1000\e[0m'
[iptables]=$'firewall rules\n \e[36m-A\e[0m \e[33mCHAIN\e[0m append rule \e[36m-L\e[0m list rules\n \e[36m-F\e[0m flush all \e[36m-X\e[0m delete chains\n \e[36m-j\e[0m \e[33mTARGET\e[0m ACCEPT/DROP/REJECT \e[36m-p\e[0m \e[33mPROTO\e[0m tcp/udp\n \e[36m-s\e[0m \e[33mIP\e[0m source address \e[36m-m\e[0m \e[33mMOD\e[0m match module\n \e[36m-n\e[0m numeric output \e[36m-v\e[0m verbose\n \e[36m--dport\e[0m \e[33mPORT\e[0m destination port \e[36m--limit\e[0m \e[33mRATE\e[0m rate limit\n \e[2miptables -A INPUT -s IP -j DROP iptables -L -n -v iptables -F\e[0m'
[eza]=$'modern ls replacement\n \e[36m-l\e[0m details (perms, owner, size, date)\n \e[36m-a\e[0m show hidden\n \e[36m-h\e[0m column headers \e[36m-R\e[0m recursive\n \e[36m-T\e[0m tree view \e[36m-L\e[0m \e[33mN\e[0m tree depth\n \e[36m-D\e[0m dirs only \e[36m-r\e[0m reverse sort\n \e[36m--sort\e[0m \e[33mFIELD\e[0m sort by field\n \e[35mvs ls\e[0m eza has git integration, color, tree built-in.\n ls is POSIX/everywhere and scriptable\n \e[2meza -la eza -T -L 2 eza --sort size\e[0m'
[pacman]=$'package manager (Arch)\n \e[36m-S\e[0m \e[33mPKG\e[0m install package \e[36m-Sy\e[0m sync repos\n \e[36m-Syu\e[0m full upgrade \e[36m-Ss\e[0m \e[33mTERM\e[0m search packages\n \e[36m-Q\e[0m list installed \e[36m-Qi\e[0m \e[33mPKG\e[0m package info\n \e[36m-Qo\e[0m \e[33mFILE\e[0m which package owns\n \e[2mpacman -Syu pacman -Ss term pacman -Qi pkg pacman -Qo /usr/bin/cmd\e[0m'
[xxd]=$'hex dump\n \e[36m-p\e[0m plain hex (no addr) \e[36m-r\e[0m reverse (hex to bin)\n \e[2mxxd file xxd -p file echo hex | xxd -r -p\e[0m'
[strings]=$'extract text from binary\n \e[36m-n\e[0m \e[33mN\e[0m minimum length \e[36m-e\e[0m \e[33mENC\e[0m encoding (l=UTF-16LE)\n \e[2mstrings binary strings -n 10 binary strings -e l binary\e[0m'
[readelf]=$'ELF binary inspector\n \e[36m-h\e[0m file header \e[36m-s\e[0m symbols\n \e[36m-a\e[0m all info\n \e[2mreadelf -h binary readelf -s binary readelf -a binary\e[0m'
[binwalk]=$'binary analysis\n \e[36m-e\e[0m extract embedded \e[36m-E\e[0m entropy analysis\n \e[2mbinwalk binary binwalk -e binary binwalk -E binary\e[0m'
[foremost]=$'file recovery\n \e[36m-i\e[0m \e[33mFILE\e[0m input image \e[36m-o\e[0m \e[33mDIR\e[0m output directory\n \e[36m-t\e[0m \e[33mTYPES\e[0m file types (jpg,pdf)\n \e[2mforemost -i image.dd foremost -t jpg,pdf -i image.dd -o output/\e[0m'
[fls]=$'filesystem listing (TSK)\n \e[36m-r\e[0m recursive \e[36m-d\e[0m deleted files only\n \e[36m-m\e[0m \e[33mPATH\e[0m mactime bodyfile\n \e[2mfls image.dd fls -r image.dd fls -d image.dd fls -r -m / image.dd\e[0m'
[rev]=$'reverse each line of text\n \e[2mrev file echo hello | rev rev <<< word\e[0m'
[bc]=$'calculator\n \e[2mbc <<< \x272+3\x27 echo \x27scale=2; 10/3\x27 | bc\e[0m'
[jobs]=$'list background jobs\n \e[36m-l\e[0m show PIDs\n \e[32m%1\e[0m refer to job 1 \e[32m%%\e[0m current job\n \e[2mjobs jobs -l\e[0m'
[fg]=$'bring job to foreground\n \e[2mfg fg %1 fg %jobname\e[0m'
[bg]=$'continue stopped job in background\n \e[2mbg bg %1\e[0m'
[kill]=$'send signal to process\n \e[36m-9\e[0m SIGKILL (force) \e[36m-15\e[0m SIGTERM (graceful)\n \e[36m-STOP\e[0m pause process \e[36m-CONT\e[0m resume process\n \e[2mkill PID kill %1 kill -9 PID kill -STOP PID\e[0m'
[wait]=$'wait for background jobs to finish\n \e[2mwait wait %1 wait $PID\e[0m'
[disown]=$'detach job from shell (survives logout)\n \e[36m-a\e[0m all jobs \e[36m-h\e[0m mark only (don\x27t remove)\n \e[2mdisown %1 disown -a\e[0m'
[sleep]=$'pause for N seconds\n \e[2msleep 1 sleep 0.5 sleep 5\e[0m'
[jq]=$'JSON processor\n \e[36m-r\e[0m raw output (no quotes) \e[36m-c\e[0m compact output\n \e[36m-e\e[0m exit 1 if null \e[36m-s\e[0m slurp into array\n \e[31m.key\e[0m extract field \e[31m.key[]\e[0m iterate array\n \e[31mselect()\e[0m filter items \e[31m| length\e[0m count elements\n \e[2mjq . file jq -r \x27.name\x27 file jq \x27.[] | select(.active)\x27 file\e[0m'
[set]=$'shell options\n \e[36m-e\e[0m exit on error \e[36m-u\e[0m error on undefined var\n \e[36m-x\e[0m trace commands \e[36m-o\e[0m \e[32mpipefail\e[0m fail on pipe error\n \e[2mset -e set -euo pipefail set -x\e[0m'
[trap]=$'handle signals\n \e[33mCMD\e[0m \e[33mSIGNAL\e[0m run CMD on SIGNAL\n \e[36m-\e[0m \e[33mSIGNAL\e[0m reset to default\n \e[33mCMD\e[0m \e[32mEXIT\e[0m run on script exit\n \e[2mtrap \x27echo caught\x27 SIGINT trap cleanup EXIT trap - SIGINT\e[0m'
[id]=$'show user/group identity\n \e[36m-u\e[0m UID only \e[36m-g\e[0m GID only\n \e[36m-G\e[0m all group IDs \e[36m-n\e[0m names instead of numbers\n \e[2mid id -u id -Gn id username\e[0m'
[mount]=$'attach filesystem\n \e[36m-o\e[0m \e[33mOPTS\e[0m options (ro, loop, noexec, nosuid, remount)\n \e[36m-t\e[0m \e[33mTYPE\e[0m filesystem type \e[36m-w\e[0m read-write\n \e[2mmount /dev/sda1 /mnt mount -o ro,loop img /mnt mount -o remount,rw /\e[0m'
[chroot]=$'change root directory\n \e[2mchroot /mnt /bin/bash chroot /mnt cmd\e[0m'
[fsck]=$'filesystem check/repair\n \e[36m-y\e[0m auto-fix \e[36m-n\e[0m dry run\n \e[2mfsck /dev/sda1 fsck -y /dev/sda1\e[0m'
[dd]=$'low-level block copy\n \e[32mif=\e[0m input file \e[32mof=\e[0m output file\n \e[32mbs=\e[0m block size \e[32mcount=\e[0m number of blocks\n \e[32mstatus=progress\e[0m show transfer rate\n \e[32mconv=noerror,sync\e[0m continue on errors\n \e[2mdd if=/dev/sda of=disk.img bs=4M status=progress\e[0m'
[lsof]=$'list open files\n \e[36m-i\e[0m network connections \e[36m-p\e[0m \e[33mPID\e[0m by process\n \e[36m-u\e[0m \e[33mUSER\e[0m by user\n \e[2mlsof -i :80 lsof -p 1234 lsof /path/to/file\e[0m'
[dmesg]=$'kernel ring buffer\n \e[36m-T\e[0m human timestamps \e[36m-w\e[0m follow live\n \e[36m-l\e[0m \e[33mLEVEL\e[0m filter by level (err, warn)\n \e[2mdmesg -T dmesg -Tw dmesg -l err\e[0m'
[cryptsetup]=$'LUKS disk encryption\n \e[32mluksOpen\e[0m unlock volume \e[32mluksClose\e[0m lock volume\n \e[32mluksFormat\e[0m format as LUKS\n \e[2mcryptsetup luksOpen /dev/sda2 crypt cryptsetup luksClose crypt\e[0m'
[vol]=$'volatility3 memory forensics\n \e[32mwindows.pslist\e[0m process list\n \e[32mwindows.netscan\e[0m network connections\n \e[32mwindows.cmdline\e[0m command lines\n \e[32mwindows.filescan\e[0m file objects\n \e[2mvol -f dump.raw windows.pslist vol -f dump.raw windows.netscan\e[0m'
[airmon-ng]=$'wireless monitor mode\n \e[32mstart\e[0m enable monitor \e[32mstop\e[0m disable monitor\n \e[32mcheck kill\e[0m stop interfering processes\n \e[2mairmon-ng start wlan0 airmon-ng stop wlan0mon airmon-ng check kill\e[0m'
[airodump-ng]=$'wireless packet capture\n \e[36m-c\e[0m \e[33mCH\e[0m channel \e[36m--bssid\e[0m \e[33mMAC\e[0m target AP\n \e[36m-w\e[0m \e[33mPREFIX\e[0m write capture\n \e[2mairodump-ng wlan0mon airodump-ng -c 6 --bssid AA:BB:CC -w cap wlan0mon\e[0m'
[aireplay-ng]=$'wireless packet injection\n \e[36m-0\e[0m \e[33mN\e[0m deauth N packets \e[36m-a\e[0m \e[33mBSSID\e[0m target AP\n \e[36m-c\e[0m \e[33mCLIENT\e[0m target client\n \e[2maireplay-ng -0 5 -a AP_MAC -c CLIENT_MAC wlan0mon\e[0m'
[aircrack-ng]=$'wireless key cracker\n \e[36m-w\e[0m \e[33mFILE\e[0m wordlist \e[36m-b\e[0m \e[33mBSSID\e[0m target AP\n \e[2maircrack-ng -w wordlist.txt -b AA:BB:CC cap-01.cap\e[0m'
[getcap]=$'show file capabilities\n \e[36m-r\e[0m recursive search\n \e[2mgetcap -r / 2>/dev/null\e[0m'
[exiftool]=$'read/write file metadata\n \e[2mexiftool image.jpg exiftool -all= image.jpg exiftool -GPS* image.jpg\e[0m'
[shred]=$'securely overwrite file \e[2m(ineffective on SSD/journaling/CoW fs)\e[0m\n \e[36m-n\e[0m \e[33mN\e[0m overwrite N times \e[36m-z\e[0m final zero pass\n \e[36m-u\e[0m remove after\n \e[2mshred -zu file shred -n 3 -zu file\e[0m'
[diff]=$'compare files line by line\n \e[36m-u\e[0m unified format \e[36m-y\e[0m side by side\n \e[36m-r\e[0m recursive (dirs) \e[36m-q\e[0m brief (files differ only)\n \e[36m-i\e[0m ignore case \e[36m-w\e[0m ignore whitespace\n \e[36m-B\e[0m ignore blank lines \e[36m--color\e[0m colorized output\n \e[2mdiff file1 file2 diff -u old new diff -rq dir1/ dir2/\e[0m'
[base64]=$'encode/decode base64\n \e[36m-d\e[0m decode \e[36m-w\e[0m \e[33mN\e[0m wrap at N columns (0=none)\n \e[2mbase64 file echo text | base64 echo encoded | base64 -d\e[0m'
[openssl]=$'crypto toolkit\n \e[32menc\e[0m encrypt/decrypt \e[32mdgst\e[0m hash/sign\n \e[32mreq\e[0m certificate request \e[32mx509\e[0m certificate utility\n \e[32ms_client\e[0m TLS test client \e[32mrand\e[0m random bytes\n \e[2mopenssl enc -aes-256-cbc -in f -out f.enc openssl s_client -connect host:443\e[0m'
[docker]=$'container runtime\n \e[32mrun\e[0m create+start \e[32mexec\e[0m run in container\n \e[32mps\e[0m list containers \e[32mimages\e[0m list images\n \e[32mbuild\e[0m build image \e[32mpull\e[0m download image\n \e[32mstop\e[0m stop container \e[32mrm\e[0m remove container\n \e[32mlogs\e[0m container logs \e[32mcompose\e[0m multi-container\n \e[2mdocker run -it ubuntu bash docker ps -a docker exec -it ID bash\e[0m'
[rsync]=$'incremental file transfer\n \e[36m-a\e[0m archive (preserve all) \e[36m-v\e[0m verbose\n \e[36m-z\e[0m compress transfer \e[36m--delete\e[0m remove extra files\n \e[36m-n\e[0m dry run \e[36m--progress\e[0m show progress\n \e[36m-e\e[0m \e[33m\'ssh\'\e[0m transfer over SSH\n \e[35mvs scp\e[0m rsync only sends changes (fast for repeat transfers).\n scp copies everything every time\n \e[2mrsync -avz src/ dst/ rsync -avz -e ssh dir/ host:dir/\e[0m'
[scp]=$'secure copy over SSH\n \e[36m-r\e[0m recursive (dirs) \e[36m-P\e[0m \e[33mPORT\e[0m custom port\n \e[35mvs rsync\e[0m scp copies everything. rsync only sends changes.\n prefer rsync for large/repeat transfers\n \e[2mscp file user@host:path scp -r dir/ user@host:path scp user@host:file .\e[0m'
[ip]=$'network configuration\n \e[32maddr\e[0m show/set addresses \e[32mlink\e[0m network devices\n \e[32mroute\e[0m routing table \e[32mneigh\e[0m ARP cache\n \e[36m-c\e[0m colorized output \e[36m-br\e[0m brief format\n \e[35mvs ifconfig\e[0m ip is modern and featureful.\n ifconfig is legacy but still on older systems\n \e[2mip addr ip route ip link ip neigh ip -c addr\e[0m'
[chown]=$'change file owner/group\n \e[36m-R\e[0m recursive \e[36m--reference\e[0m=\e[33mFILE\e[0m copy ownership\n \e[33mUSER:GROUP\e[0m owner and group \e[33mUSER\e[0m owner only\n \e[2mchown user file chown user:group file chown -R user:group dir/\e[0m'
[apt]=$'package manager (Debian/Ubuntu)\n \e[32minstall\e[0m install package \e[32mremove\e[0m uninstall package\n \e[32mupdate\e[0m refresh repo lists \e[32mupgrade\e[0m upgrade all\n \e[32msearch\e[0m find packages \e[32mshow\e[0m package info\n \e[36m-y\e[0m auto-confirm\n \e[2mapt install pkg apt update && apt upgrade apt search term\e[0m'
[dnf]=$'package manager (Fedora/RHEL)\n \e[32minstall\e[0m install package \e[32mremove\e[0m uninstall package\n \e[32mupgrade\e[0m upgrade all \e[32msearch\e[0m find packages\n \e[32minfo\e[0m package info \e[32mlist\e[0m list packages\n \e[36m-y\e[0m auto-confirm\n \e[2mdnf install pkg dnf upgrade dnf search term\e[0m'
[netstat]=$'network statistics (legacy)\n \e[36m-t\e[0m TCP \e[36m-u\e[0m UDP\n \e[36m-l\e[0m listening \e[36m-n\e[0m numeric ports\n \e[36m-p\e[0m show process \e[36m-r\e[0m routing table\n \e[35mvs ss\e[0m netstat is deprecated. ss is faster and modern.\n use ss -tlnp instead of netstat -tlnp\n \e[2mnetstat -tlnp netstat -tunapl netstat -r\e[0m'
[readlink]=$'show symlink target\n \e[36m-f\e[0m canonicalize (full path) \e[36m-e\e[0m canonicalize (must exist)\n \e[35mvs realpath\e[0m readlink shows the link target.\n realpath resolves the full canonical path\n \e[2mreadlink link readlink -f link readlink -e link\e[0m'
[alias]=$'create command shortcuts\n \e[33mNAME\e[0m=\e[33m\x27CMD\x27\e[0m define alias \e[2m(no alias)\e[0m list all\n \e[2malias ll=\x27ls -la\x27 alias gs=\x27git status\x27 alias\e[0m'
[unalias]=$'remove alias\n \e[36m-a\e[0m remove all aliases\n \e[2munalias ll unalias -a\e[0m'
[watch]=$'run command repeatedly\n \e[36m-n\e[0m \e[33mN\e[0m interval (seconds) \e[36m-d\e[0m highlight changes\n \e[36m-t\e[0m no title header \e[36m-g\e[0m exit on change\n \e[2mwatch -n 2 df -h watch -d free -h watch -n 5 \x27ps aux | head\x27\e[0m'
[stat]=$'file metadata\n \e[36m-c\e[0m \e[33mFMT\e[0m custom format \e[36m-f\e[0m filesystem info\n \e[33m%s\e[0m size \e[33m%U\e[0m owner \e[33m%G\e[0m group \e[33m%a\e[0m octal perms \e[33m%y\e[0m modified\n \e[2mstat file stat -c %s file stat -c \x27%a %U %G\x27 file\e[0m'
[umask]=$'default permission mask\n \e[32m022\e[0m files 644, dirs 755 \e[32m077\e[0m files 600, dirs 700\n \e[36m-S\e[0m symbolic output\n \e[2mumask umask 022 umask -S\e[0m'
[nohup]=$'run command immune to hangups\n keeps running after terminal closes (output to nohup.out)\n \e[35mvs disown\e[0m nohup starts immune. disown detaches an already-running job\n \e[2mnohup ./script.sh & nohup cmd > out.log 2>&1 &\e[0m'
[file]=$'identify file type\n \e[36m-i\e[0m MIME type \e[36m-b\e[0m brief (no filename)\n \e[2mfile image.png file binary file -i document.pdf\e[0m'
[ssh-keygen]=$'generate SSH key pairs\n \e[36m-t\e[0m \e[33mTYPE\e[0m key type (ed25519, rsa) \e[36m-b\e[0m \e[33mBITS\e[0m key size\n \e[36m-C\e[0m \e[33mTEXT\e[0m comment \e[36m-f\e[0m \e[33mFILE\e[0m output file\n \e[35med25519\e[0m modern, fast, secure (preferred)\n \e[35mrsa 4096\e[0m compatible everywhere\n \e[2mssh-keygen -t ed25519 ssh-keygen -t rsa -b 4096\e[0m'
[basename]=$'strip directory from path\n \e[33mSUFFIX\e[0m also strip suffix\n \e[2mbasename /var/log/syslog basename file.tar.gz .tar.gz\e[0m'
[dirname]=$'strip filename from path\n \e[2mdirname /var/log/syslog dirname /home/user/file.txt\e[0m'
)
_mp_show() {
local cmd=$1 _t; local data="${MANPAGE[$cmd]}"
printf -v _t "%s %s\n" "${W}${B}${cmd}${N}" "${D}- ${data%%$'\n'*}${N}"; _EBUF+=$_t
printf -v _t '%s\n' "${data#*$'\n'}"; _EBUF+=$_t
}
declare -A EXP=(
[pwd]="print working directory (where you are)"
[echo]="print text to stdout" [cat]="concatenate and display file contents" [ls]="list directory contents"
[cd]="change directory" [cp]="copy files" [mv]="move/rename files" [rm]="delete files permanently"
[mkdir]="create directory" [rmdir]="remove empty directory" [touch]="create file/update timestamp"
[which]="find command location" [type]="describe command" [alias]="create shortcut" [unalias]="remove alias"
[watch]="repeat command on interval" [ssh-keygen]="generate SSH keys" [ssh-copy-id]="install SSH key on remote"
[head]="head -N FILE: first ${Y}N${N} lines (default 10)"
[tail]="tail -N FILE: last ${Y}N${N} lines (-f to follow)"
[grep]="grep PATTERN FILE: search lines matching pattern"
[find]="find PATH -name 'x': locate files by name/size/date"
[wc]="word count: -l lines, -w words, -c bytes"
[sort]="sort lines alphabetically (-n numeric, -r reverse, -k N by column ${Y}N${N})"
[uniq]="filter adjacent duplicates (-c count, -d dupes only — sort -u can't do these)" [cut]="cut -d',' -fN: extract column N (simple columns — awk for logic/math)"
['--sort=-%cpu']="sort by CPU% descending (- = descending)"
['--sort=-%mem']="sort by memory% descending"
[tr]="tr FROM TO: map chars (tr a-z A-Z = lowercase->uppercase)"
[diff]="compare files" [tar]="archive tool" [tee]="split output: file AND screen (>> = file only)" [xargs]="build commands from stdin"
[chmod]="change permissions" [kill]="send signal to process (default: terminate)" [jobs]="list background jobs" [fg]="foreground job"
[bg]="background job" [sleep]="pause N seconds" [make]="build tool" [gcc]="C compiler"
[date]="show/set date" [du]="disk usage" [ps]="list processes"
[dd]="low-level copy" [strings]="extract text from binary" [base64]="encode/decode base64"
[rg]="ripgrep: fast grep (respects .gitignore, recursive by default — grep is POSIX/everywhere)"
[fd]="fast find (regex default, respects .gitignore — find is POSIX/everywhere, more powerful predicates)"
[eza]="modern ls (git integration, color — ls is POSIX/everywhere)"
[bat]="cat with highlighting (use cat for piping, bat for reading)"
[sd]="simple sed (literal strings by default — sed uses regex)" [dust]="visual du (bar chart — du is POSIX/scriptable)"
[tree]="directory tree" [sed]="stream editor" [xxd]="hex dump"
[md5sum]="MD5 hash (weak)" [sha256sum]="SHA256 hash" [nmap]="port scanner" [hydra]="password cracker"
[hashcat]="GPU hash cracker" [john]="John the Ripper" [tshark]="terminal wireshark"
[masscan]="fast port scanner" [airmon-ng]="WiFi monitor mode"
[airodump-ng]="capture 802.11" [aireplay-ng]="inject packets" [aircrack-ng]="crack WPA/WEP"
[nc]="netcat" [ncat]="nmap netcat (supports -e)" [smbclient]="SMB client" [lynis]="security audit" [binwalk]="firmware analysis"
[foremost]="file recovery" [fls]="list disk image" [exiftool]="file metadata" [readelf]="ELF info"
[r2]="radare2 RE framework" [vol]="memory forensics (volatility3)" [mactime]="filesystem timeline"
[proxychains]="proxy traffic" [procs]="modern ps"
[awk]="pattern scanning & processing" [jq]="JSON processor" [curl]="transfer data (HTTP/FTP)"
[ssh]="secure shell" [scp]="secure copy (prefer rsync for large/repeat transfers)" [rsync]="incremental transfer (only sends changes — always prefer over scp)"
[zip2john]="extract hash from zip" [pdf2john]="extract hash from pdf" [rar2john]="extract hash from rar"
[openssl]="crypto toolkit" [socat]="multipurpose relay"
[ln]="create links" [readlink]="show link target" [pgrep]="find process by name" [pkill]="kill by name"
[pidof]="get PID by name" [pstree]="process tree" [ip]="network config" [ss]="socket statistics"
[whoami]="print current username" [chgrp]="change group ownership" [file]="identify file type"
[chown]="change file owner/group" [stat]="show file metadata" [umask]="set default permissions"
[nohup]="run immune to hangups" [disown]="detach job from shell" [basename]="strip directory from path" [dirname]="strip filename from path"
[lsof]="list open files" [systemctl]="systemd control" [journalctl]="systemd logs"
[wget]="download files (resume, mirror — curl is for APIs)" [unlink]="remove exactly one file/link (safer than rm for single targets)"
['$$']="current shell PID" ['$?']="last exit code" ['$!']="last background PID"
['$#']="argument count" ['$@']="all arguments" ['$0']="script name"
['!!']="repeat last command" ['!$']="last arg of prev cmd" ['!^']="first arg of prev cmd" ['!*']="all args of prev cmd"
['>']="write stdout to file (overwrite — output disappears from terminal)" ['>>']="append stdout to file (output disappears from terminal — use tee to see it too)"
['2>']="redirect stderr to file" ['&>']="redirect stdout+stderr to file"
['/dev/null']="black hole: discards anything written to it" ['2>&1']="merge stderr into stdout" ['>&2']="send stdout to stderr" ['1>&2']="send stdout to stderr"
['<']="input redirect: feed FILE contents into command"
['<<<']="here-string: feed a STRING into command"
['.']="current directory" ['..']="parent directory" ['~']="home directory" ['/']="root directory (or path separator)"
['*']="wildcard: matches any characters" ['?']="wildcard: matches single character"
['*.py']="glob: all .py files" ['*.log']="glob: all .log files" ['*.txt']="glob: all .txt files"
['*.sh']="glob: all .sh files" ['*.conf']="glob: all .conf files"
["'*.py'"]="glob: all .py files (quoted)" ["'*.log'"]="glob: all .log files (quoted)"
["'*.txt'"]="glob: all .txt files (quoted)" ["'*.sh'"]="glob: all .sh files (quoted)"
["'*.conf'"]="glob: all .conf files (quoted)" ["'*'"]="glob: all files (quoted)"
["'...'"]="single quotes: literal string, no expansion" ['"..."']="double quotes: allows \$var expansion"
["'hello'"]="literal string 'hello'"
['|']="pipe: cmd1 | cmd2 (stdout->stdin)" ['|&']="pipe stdout+stderr"
['&&']="run next only if previous succeeds" ['||']="run next only if previous fails"
['&']="run in background (returns immediately)"
['a-z']="character range: all lowercase letters" ['A-Z']="character range: all uppercase letters" ['0-9']="character range: all digits"
['${#var}']="string length" ['${var:-}']="default if unset" ['${var:=}']="set if unset"
['${var:?}']="error if unset" ['${var:+}']="alt if set" ['${var#}']="remove prefix (short)"
['${var##}']="remove prefix (long)" ['${var%}']="remove suffix (short)" ['${var%%}']="remove suffix (long)"
['${var/}']="replace first" ['${var//}']="replace all" ['${var:n}']="substring from n"
['${var:n:m}']="substring n to m" ['${var,,}']="lowercase" ['${var^^}']="uppercase"
[declare]="declare variable type" [mapfile]="read lines into array (same as readarray)" [readarray]="read lines into array (same as mapfile)"
['${arr[@]}']="all array elements" ['${#arr[@]}']="array length" ['${!arr[@]}']="array indices"
[if]="conditional execution" [then]="if body start" [else]="if alternative" [elif]="else if"
[fi]="end if" [for]="loop over items" [while]="loop while true" [until]="loop until true"
[do]="loop body start" [done]="end loop" [case]="pattern matching" [esac]="end case"
[function]="define function" [local]="local variable" [return]="function return"
[trap]="handle signals" [set]="shell options" [shopt]="bash options"
[strace]="trace syscalls" [ltrace]="trace library calls" [watch]="run command repeatedly"
[inotifywait]="watch filesystem events" [parallel]="run jobs in parallel" [flock]="file locking"
[timeout]="limit command runtime" [chroot]="change root dir" [fsck]="filesystem check"
[cryptsetup]="LUKS encryption" [iptables]="IPv4 firewall" [nftables]="netfilter firewall"
[shred]="secure file delete" [smartctl]="disk SMART info" [gcore]="dump process memory"
[coproc]="coprocess (bidirectional pipe)" [mkfifo]="create named pipe" [fuser]="find process using file"
[renice]="change process priority" [cgcreate]="create cgroup" [cgexec]="run in cgroup"
[enum4linux]="SMB enumeration"
[chkrootkit]="rootkit checker" [rkhunter]="rootkit hunter"
[bc]="calculator" [rev]="reverse chars per line" [docker]="container runtime" [crontab]="schedule tasks"
[tcpdump]="packet capture" [iw]="wireless config"
[radare2]="reverse engineering framework (r2)" [volatility]="memory forensics (vol)"
[volatility3]="memory forensics (vol)" [vol.py]="memory forensics (legacy)"
[vol3]="memory forensics (volatility3)" [vol3.py]="memory forensics (volatility3)"
[photorec]="file recovery (carving)" [scalpel]="file carving tool"
[egrep]="extended grep (use grep -E)" [hostnamectl]="show/set hostname (systemd)"
[findmnt]="list mounted filesystems" [printenv]="print environment variables"
[shasum]="SHA checksum" [sha1sum]="SHA-1 hash"
[git]="version control" [clone]="copy repository" [commit]="save changes" [push]="upload to remote"
[pull]="download from remote" [fetch]="download without merge" [merge]="combine branches"
[rebase]="reapply commits" [stash]="save changes temporarily" [branch]="manage branches"
[checkout]="switch branch/restore" [switch]="switch branches" [restore]="restore files"
[reset]="undo commits" [revert]="undo with new commit" [cherry-pick]="apply specific commit"
[bisect]="binary search for bug" [reflog]="reference logs" [blame]="show line authors"
[tag]="mark releases" [remote]="manage remotes" [log]="show history"
[tmux]="terminal multiplexer" [attach]="connect to session"
[detach]="disconnect from session" [new-session]="create session" [kill-session]="destroy session"
[split-window]="create pane" [select-pane]="switch pane" [resize-pane]="change pane size"