
Folosesc sisteme Unix și Linux de aproape 20 de ani. Când m-am trezit cu un laptop Windows pentru o vreme, m-am simțit pierdut și am instalat rapid Windows Subsystem pentru Linux. Și totuși, numărul de comenzi pe care le cunosc și le folosesc în mod regulat este surprinzător de scurt: cd, ls, rm, pwd, mkdir, source, node, npm și, desigur, git. Nici atunci nu cred că într-adevăr „știi” git.
În cartea sa excelentă „Nu mă face să gândesc Revizuită”, Steve Krug descrie cum
Confruntați cu orice fel de tehnologie, foarte puțini oameni își iau timp să citească instrucțiunile. În schimb, mergem înainte și ne încurcăm, creând propriile noastre povești vag plauzibile despre ceea ce facem și de ce funcționează.
Cartea lui Krug este despre utilizarea web și mobilă, dar ideea pe care o face aici se aplică în toate domeniile tehnologice. Și este sigur să spun că m-am „încurcat” cu Unix. Dacă m-ai întreba dacă ceva este o comandă Unix sau o parte a shell-ului, probabil că aș ridica din umeri și aș spune ceva de genul „Eu… nu știu asta!?”.
Până de curând, StackOverflow a fost întotdeauna cârja pe care să mă întorc. Din frecvența cu care apare în răspunsuri știu asta find este o comandă foarte utilă și versatilă. Cu toate acestea, șansele ca eu să-mi amintesc cum să-l folosesc fără să-l caut nu se pot distinge de 0.
Dar acum avem alternative sub forma diferitelor modele de limbaj mari sau, mai precis, asistenți AI multimodali (cum ar fi Claude și ChatGPT) și agenți (cum ar fi Claude Code și Codex). M-aș descrie ca fiind agnostic de AI: văd beneficii, îmi fac griji pentru probleme. Așa că, cu un anumit grad de scepticism (aș spune sănătos), am început să-l folosesc pe Claude pentru a mă ajuta să scriu câteva funcții shell pentru a automatiza sarcinile pe care le fac sau aș dori să le fac în mod regulat.
Eu spun „eu” a scris. Realitatea este, desigur, că l-am rugat pe Claude să scrie și să rescrie lucruri pentru mine și apoi m-am întrebat de ce a făcut ceea ce a făcut. Probabil că asta face să pară că l-am evaluat pe Claude pentru o slujbă, dar de fapt vreau să înțeleg ce s-a scris și, așa cum am descris deja, nu mă pricep prea bine la aceste lucruri.
Sarcina unu: repararea unui comun git greşeală
Una dintre derapajele mele cele mai frecvente cu git este să comite modificări într-o ramură (de obicei principală) care au fost menite să meargă într-o nouă ramură a caracteristicilor. Știu o modalitate de a vă recupera din asta: creați noua ramură pe care am vrut să o creez tot timpul; face o rebase interactivă pentru a elimina commit-ul din main; comutați la noua ramură de caracteristici pe care tocmai am creat-o. Nu există atât de mulți pași în proces, dar este totuși un pic cam greoi. Să-l întrebăm pe Claude:
Tim
Dacă git (commit) într-o ramură greșită, există o modalitate rapidă de a muta modificarea într-o nouă ramură și de a o elimina din ultimul commit din ramura curentă?
Claude
Da, presupunând că nu ați împins încă:
# Create and switch to the correct branch from current state git switch -c correct-branch # Go back to the wrong branch and undo the commit (keeps changes staged) git switch wrong-branch git reset --soft HEAD~1 # Discard the changes git restore --staged . git restore .
Sau dacă doriți să o păstrați și mai simplă – dacă commit-ul este doar pe ramura greșită și nu este împins:
git switch -c correct-branch git switch wrong-branch git reset --hard HEAD~1
Comutatorul -c creează noua ramură îndreptată către aceeași comitere, astfel încât munca dvs. este păstrată acolo. Apoi o dezlipiți de ramura greșită cu reset –hard.
Aici ne-am lovit de câteva probleme.
- Niciuna dintre sugestiile lui Claude nu a făcut pasul final al ceea ce mi-am dorit: să mă mut înapoi la noua, corectă, ramură. Acest lucru este în întregime asupra mea. Am uitat sa intreb (si nu am observat un pic). Fac așa ceva destul de mult.
- Chiar și pentru sarcini simple, răspunsurile lui Claude pot fi destul de verbose.
Problema aici nu este una practică în ceea ce privește utilizarea lui Claude sau a unui agent alternativ, adevărata problemă este că este destul de dificil să scrii o postare pe blog de o lungime sensibilă, care să meargă oriunde în timp ce povestești cu acuratețe conversația pe care o ai cu Claude. Deci, ceea ce urmează este mai degrabă o parafrază decât o transcriere; scurtat de dragul sanatatii tale si a mea.
Tim
Pot folosi git switch – în schimb?
Claude
Da, și e mai curat. git switch - vă scutește să introduceți în mod explicit numele greșit al ramurilor.
Tim
Pare genul de lucru pe care ar trebui să-l transform într-o funcție Bash
Claude
De acord. Ceva de genul acesta (cu o verificare suplimentară de siguranță):
git-move-last() {
if ( -z "$1" ); then
echo "Usage: git-move-last "
return 1
fi
git switch -c "$1"
git switch -
git reset --hard HEAD~1
}
Tim
M-am dus pentru git-dehead. Dacă vreau să mă deplasez la noua ramură la sfârșit, adaug un alt comutator git -?
Claude
Ha 🪓.
Exact:
git-behead() {
if ( -z "$1" ); then
echo "Usage: git-behead "
return 1
fi
git switch -c "$1"
git switch -
git reset --hard HEAD~1
git switch -
}
Tim
Ok, încă o idee. Să presupunem că nu m-am uitat la ceea ce fac și vreau să elimin mai mult decât ultima comitere la o nouă ramură. Putem recrea cele de mai sus, dar cu un număr de comitere opțional, care este implicit 1?
Claude
git-behead() {
if ( -z "$1" ); then
echo "Usage: git-behead (commit-count)"
return 1
fi
local count="${2:-1}"
git switch -c "$1"
git switch -
git reset --hard HEAD~"$count"
git switch -
}
Tim
Ați putea comenta ceva despre care credeți că nu se explică de la sine?
Claude
git-behead() {
if ( -z "$1" ); then
echo "Usage: git-behead (commit-count)"
return 1
fi
local count="${2:-1}" # default to 1 commit if not specified
git switch -c "$1" # create new branch at current HEAD
git switch - # switch back to original branch
git reset --hard HEAD~"$count" # remove commits from original branch
git switch - # switch to new branch
}
Punerea acestui lucru în fișierul meu .bashrc îmi oferă o comandă simplă pe care o pot invoca ori de câte ori fac greșeala pe care o fac în mod regulat și un argument opțional pe care să îl folosesc ori de câte ori nu am fost atent la ramura în care mă aflu pentru o perioadă lungă de timp. De asemenea, este suficient de scurt încât să cred că urmez fiecare rând, în ciuda cunoștințelor mele limitate despre Bash, și există comentarii acolo pentru atunci când viitorul eu am uitat. Această conversație a durat aproximativ cincisprezece minute și a inclus mai multe întrebări decât am inclus aici, plus timp petrecut rulând diverse teste ad-hoc cu diferite iterații ale funcției. Pare o utilizare destul de eficientă a timpului meu.
Sarcina a doua: listarea fișierelor imagine
Fac multă vizualizare de date, mă ocup de arta acestui site și fac fotografii pentru conferințele noastre. Pe scurt, mă ocup de o mulțime de imagini în diverse formate. Ar fi frumos să puteți enumera doar imaginile și proprietățile lor cheie într-o anumită locație cu o funcție simplă. Să-l întrebăm pe Claude:
Tim
Îmi puteți scrie un script Bash pentru afișarea unei liste de imagini într-un director care arată numele fișierului, lățimea, înălțimea și dimensiunea (în kB) și nimic altceva?
Claude
Iată un script curat care utilizează identificarea din ImageMagick (disponibilă pe scară largă) cu o rezervă pentru dimensiunea fișierului:
#!/usr/bin/env bash
DIR="${1:-.}"
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
exit 1
fi
shopt -s nullglob nocaseglob
files=("$DIR"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif})
shopt -u nullglob nocaseglob
if (( ${#files(@)} -eq 0 )); then
echo "No images found in: $DIR" >&2
exit 0
fi
printf "%-40s %8s %8s %10s\n" "FILENAME" "WIDTH" "HEIGHT" "SIZE (kB)"
printf "%-40s %8s %8s %10s\n" "--------" "-----" "------" "---------"
for f in "${files(@)}"; do
name="$(basename "$f")"
read -r width height < <(identify -format "%w %h" "$f" 2>/dev/null | head -1)
size_kb=$(awk "BEGIN { printf \"%.1f\", $(wc -c < "$f") / 1024 }")
printf "%-40s %8s %8s %10s\n" "$name" "$width" "$height" "$size_kb"
done
Formate acceptate: jpg, jpeg, png, gif, webp, bmp, tiff, avif.
Exemplu de ieșire:
FILENAME WIDTH HEIGHT SIZE (kB) -------- ----- ------ --------- banner.png 1200 630 84.3 avatar.jpg 400 400 32.1 hero.webp 1920 1080 210.7
Am cerut un scenariu, am primit un scenariu. Am vrut o funcție. Acesta este, din nou, pe mine.
Tim
Ne pare rău, putem face din asta o funcție mai degrabă decât un script?
Claude
Schimbările cheie: shebang eliminat, exit înlocuit cu return, iar variabilele sunt acum locale. Pentru a-l folosi, aprovizionați-l în shell și apelați-l ca de obicei:
imgls() {
local dir="${1:-.}"
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
return 1
fi
shopt -s nullglob nocaseglob
local files=("$dir"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif})
shopt -u nullglob nocaseglob
if (( ${#files(@)} -eq 0 )); then
echo "No images found in: $dir" >&2
return 0
fi
printf "%-40s %8s %8s %10s\n" "FILENAME" "WIDTH" "HEIGHT" "SIZE (kB)"
printf "%-40s %8s %8s %10s\n" "--------" "-----" "------" "---------"
local f name width height size_kb
for f in "${files(@)}"; do
name="$(basename "$f")"
read -r width height < <(identify -format "%w %h" "$f" 2>/dev/null | head -1)
size_kb=$(awk "BEGIN { printf \"%.1f\", $(wc -c < "$f") / 1024 }")
printf "%-40s %8s %8s %10s\n" "$name" "$width" "$height" "$size_kb"
done
}
Conversația continuă puțin de aici și funcția devine din ce în ce mai detaliată. Ar fi plictisitor să arăți întreaga conversație, dar pentru a rezuma, „noi”:
- Adăugați suport pentru SVG, inclusiv extragerea dimensiunilor din
viewBoxatribut. - Remediați o eroare cu dimensiunile GIF.
- Modificați textul antetului.
- Adăugați suport pentru căutarea recursivă a unui director.
- Remediați aspectul când efectuați o căutare recursivă.
- Rotunjiți valorile din coloana SIZE
- Adaugă comentarii.
Rezultatul final:
imgls() {
local recursive=0
# Parse flags — currently only -r (recursive) is supported
while getopts ":r" opt; do
case $opt in
r) recursive=1 ;;
*) echo "Usage: imgls (-r) (directory)" >&2; return 1 ;;
esac
done
# Shift past any parsed flags so $1 is the directory argument
shift $((OPTIND - 1))
OPTIND=1
# Use first argument as directory, default to current directory if omitted
local dir="${1:-.}"
# identify is provided by imagemagick and used to read raster image dimensions
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
return 1
fi
local exts=("jpg" "jpeg" "png" "gif" "webp" "bmp" "tiff" "tif" "avif" "svg")
local files=()
if (( $recursive -eq 1 )); then
# Build a find command matching all extensions, case-insensitively
local find_args=("$dir" "-type" "f" "(")
for i in "${!exts(@)}"; do
(( $i -gt 0 )) && find_args+=("-o")
find_args+=("-iname" "*.${exts($i)}")
done
find_args+=(")")
while IFS= read -r f; do
files+=("$f")
done < <(find "${find_args(@)}" | sort)
else
# nullglob: unmatched globs expand to nothing instead of being treated as literals
# nocaseglob: match extensions case-insensitively (e.g. .JPG as well as .jpg)
shopt -s nullglob nocaseglob
files=("$dir"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif,svg})
shopt -u nullglob nocaseglob
fi
if (( ${#files(@)} -eq 0 )); then
echo "No images found in: $dir" >&2
return 0
fi
# In recursive mode show the relative path, otherwise just the filename
printf "%-40s %11s %12s %10s\n" "FILENAME" "WIDTH (px)" "HEIGHT (px)" "SIZE (kB)"
printf "%-40s %11s %12s %10s\n" "--------" "----------" "-----------" "---------"
local f name width height size_kb viewbox current_subdir="" subdir
for f in "${files(@)}"; do
if (( $recursive -eq 1 )); then
# Print a header line when we enter a new subdirectory
subdir="$(dirname "${f#$dir/}")"
if (( "$subdir" != "$current_subdir" )); then
(( -n "$current_subdir" )) && echo ""
echo "${subdir}/"
current_subdir="$subdir"
fi
name="$(basename "$f")"
else
name="$(basename "$f")"
fi
# wc -c counts raw bytes; awk converts to kB with one decimal place
size_kb=$(awk "BEGIN { printf \"%d\", $(wc -c < "$f") / 1024 }")
if (( "${f,,}" == *.svg )); then
# SVGs are XML so we can parse dimensions directly without imagemagick.
# Prefer viewBox (e.g. "0 0 100 200") as it's the most reliable source
# of the intended coordinate space; fall back to width/height attributes.
viewbox=$(grep -oi 'viewBox="(^")*"' "$f" 2>/dev/null | head -1 | grep -oi '"(^")*"' | tr -d '"')
if (( -n "$viewbox" )); then
# viewBox is "min-x min-y width height" — fields 3 and 4
width=$(echo "$viewbox" | awk '{printf "%g", $3}')
height=$(echo "$viewbox" | awk '{printf "%g", $4}')
else
width=$(grep -oi 'width="(^")*"' "$f" 2>/dev/null | head -1 | grep -oi '"(^")*"' | tr -d '"px"')
height=$(grep -oi 'height="(^")*"' "$f" 2>/dev/null | head -1 | grep -oi '"(^")*"' | tr -d '"px"')
fi
# ${var:--} expands to "-" if var is empty or unset
width="${width:--}"
height="${height:--}"
else
# (0) selects only the first frame, preventing animated GIFs from
# producing one line of output per frame
read -r width height < <(identify -format "%w %h" "${f}(0)" 2>/dev/null)
fi
printf "%-40s %11s %12s %10s\n" "$name" "$width" "$height" "$size_kb"
done
}
„Timpul de dezvoltare” aici a fost mai lung și m-aș zăbovi dacă aș spune că am înțeles fiecare rând de acest funcţie. Dar înțeleg principiile generale și înțeleg ce poate și ce nu poate face. Și posibilitatea de a enumera toate imaginile (de aproape toate tipurile relevante) într-un director cu proprietățile lor cheie într-un format tabel frumos este un câștig de productivitate foarte frumos pentru mine.
Gânduri finale
Discuțiile despre AI tind cu ușurință spre extreme: „este uimitor”, „este un dezastru”, „este inutil”. Acest lucru este deosebit de inutil atunci când vă concentrați pe cât de cu succes sau altfel execută o singură sarcină. Tot ce am făcut cu adevărat în această postare este să creăm câteva scripturi Bash. Pentru mine, AI-ul în cauză, Claude, a fost folositplin. Nici mai mult, nici mai puțin. În ciuda acestui fapt, procesul m-a făcut să mă gândesc mult la modul în care se schimbă modul în care interacționăm cu mașinile. Deși este ușor să personificați prea mult AI, această linie din filmul care a dat titlul acestei postări de blog pare acum, în 2026, să fi fost prevestitoare:
Apariția computerelor și acumularea ulterioară de date incalculabile a dat naștere unui nou sistem de memorie și gândire paralel cu al tău.
Pentru actualizări și revizuiri ale acestui articol, consultați postarea inițială
