Si të përdorni eval në skriptet e Linux Bash


Nga të gjitha komandat Bash, i varfëri i vjetër eval ndoshta ka reputacionin më të keq. I justifikuar, apo thjesht shtyp i keq? Ne diskutojmë përdorimin dhe rreziqet e këtyre komandave më pak të dashura të Linux.

Ne duhet të flasim për eval

I përdorur pa kujdes, eval mund të çojë në sjellje të paparashikueshme dhe madje edhe në pasiguri të sistemit. Nga tingujt e tij, ndoshta nuk duhet ta përdorim, apo jo? Epo jo mjaft.

Mund të thuash diçka të ngjashme për automobilat. Në duar të gabuara, ata janë një armë vdekjeprurëse. Njerëzit i përdorin ato në bastisje dash dhe si automjete për t'u larguar. A duhet të ndalojmë të gjithë përdorimin e makinave? Jo sigurisht qe jo. Por ato duhet të përdoren siç duhet dhe nga njerëz që dinë t'i drejtojnë.

Mbiemri i zakonshëm i aplikuar për eval është e keqe. Por gjithçka varet nga mënyra se si përdoret. Komanda eval mbledh vlerat nga një ose më shumë variabla. Krijon një varg komande. Më pas e ekzekuton atë komandë. Kjo e bën të dobishme kur ju duhet të përballeni me situata ku përmbajtja e një komande rrjedh në mënyrë dinamike gjatë ekzekutimit të skenarit tuaj.

Problemet lindin kur një skript shkruhet për të përdorur eval në një varg që është marrë nga diku jashtë skripti. Mund të shtypet nga një përdorues, të dërgohet përmes një API, të etiketohet në një kërkesë HTTPS ose kudo tjetër jashtë skriptit.

Nëse vargu në të cilin do të punojë eval nuk është nxjerrë në nivel lokal dhe programatik, ekziston rreziku që vargu të përmbajë udhëzime të ngulitura me qëllim të keq ose hyrje të tjera të formuara keq. Natyrisht, ju nuk dëshironi që eval të ekzekutojë komanda me qëllim të keq. Pra, për të qenë të sigurt, mos përdorni eval me vargjet e krijuara nga jashtë ose hyrjen e përdoruesit.

Hapat e parë me eval

Komanda eval është një komandë e integruar e guaskës Bash. Nëse Bash është i pranishëm, eval do të jetë i pranishëm.

eval bashkon parametrat e tij në një varg të vetëm. Do të përdorë një hapësirë të vetme për të ndarë elementët e lidhur. Ai vlerëson argumentet dhe më pas ia kalon të gjithë vargun në shell për ta ekzekutuar.

Le të krijojmë një variabël të quajtur wordcount.

wordcount="wc -w raw-notes.md"

Variabla e vargut përmban një komandë për të numëruar fjalët në një skedar të quajtur raw-notes.md.

Ne mund të përdorim eval për të ekzekutuar atë komandë duke ia kaluar vlerën të ndryshores.

eval "$wordcount"

Komanda ekzekutohet në guaskën aktuale, jo në një nënshell. Këtë mund ta tregojmë lehtësisht. Ne kemi një skedar të shkurtër teksti të quajtur variables.txt. Ai përmban këto dy rreshta.

first=How-To
second=Geek

Ne do të përdorim cat për t'i dërguar këto rreshta në dritaren e terminalit. Më pas do të përdorim eval për të vlerësuar një komandë cat në mënyrë që të zbatohen udhëzimet brenda skedarit të tekstit. Kjo do të vendosë variablat për ne.

cat variables.txt
eval "$(cat variables.txt)"
echo $first $second

Duke përdorur echo për të printuar vlerat e variablave, mund të shohim se komanda eval ekzekutohet në guaskën aktuale, jo në një nënshell.

Një proces në një nënshell nuk mund të ndryshojë mjedisin e guaskës së prindit. Për shkak se eval ekzekutohet në guaskën aktuale, variablat e vendosur nga eval janë të përdorshme nga shell që ka nisur komandën eval.

Vini re se nëse përdorni eval në një skript, guaska që do të ndryshohej nga eval është nënshtresa në të cilën po ekzekutohet skripti, jo guaska që e nisi atë.

Përdorimi i variablave në vargun e komandës

Mund të përfshijmë variabla të tjerë në vargjet e komandës. Do të vendosim dy variabla për të mbajtur numra të plotë.

num1=10 
num2=7

Ne do të krijojmë një variabël për të mbajtur një komandë expr që do të kthejë shumën e dy numrave. Kjo do të thotë që ne duhet të aksesojmë vlerat e dy ndryshoreve të plota në komandë. Vini re të pasmet rreth deklaratës expr.

add="`expr $num1 + $num2`"

Ne do të krijojmë një komandë tjetër për të na treguar rezultatin e deklaratës expr.

show="echo"

Vini re se nuk kemi nevojë të përfshijmë një hapësirë në fund të vargut echo, as në fillim të vargut expr. eval kujdeset për këtë.

Dhe për të ekzekutuar të gjithë komandën ne përdorim:

eval $show $add

Vlerat e variablave brenda vargut expr zëvendësohen në varg nga eval , përpara se të kalohet në shell për t'u ekzekutuar.

Qasja e variablave brenda variablave

Ju mund t'i caktoni një vlerë një ndryshoreje dhe më pas t'i caktoni emrin të asaj ndryshore në një variabël tjetër. Duke përdorur eval, mund të përdorni vlerën që mbahet në ndryshoren e parë, nga emri i saj që është vlera e ruajtur në variablin e dytë. Një shembull do t'ju ndihmojë ta zgjidhni atë.

Kopjojeni këtë skript në një redaktues dhe ruajeni si skedar të quajtur assign.sh.

#!/bin/bash

title="How-To Geek"
webpage=title
command="echo"
eval $command \${$webpage}

Duhet ta bëjmë atë të ekzekutueshëm me komandën chmod.

chmod +x assign.sh

Ju do të duhet ta bëni këtë për çdo skript që kopjoni nga ky artikull. Thjesht përdorni emrin e duhur të skriptit në secilin rast.

Kur ekzekutojmë skriptin tonë, ne shohim tekstin nga ndryshorja title edhe pse komanda eval përdor variablin webpage.

./assign.sh

Shenja e dollarit e arratisur $ dhe kllapat {} bëjnë që eval të shikojë vlerën e mbajtur brenda ndryshores, emri i së cilës ruhet në faqen e internetit ndryshore.

Përdorimi i variablave të krijuar në mënyrë dinamike

Ne mund të përdorim eval për të krijuar variabla në mënyrë dinamike. Ky skenar quhet loop.sh.

#!/bin/bash

total=0
label="Looping complete. Total:"

for n in {1..10}
do
  eval x$n=$n
  echo "Loop" $x$n
  ((total+=$x$n))
done

echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10

echo $label $total

Krijon një variabël të quajtur total e cila mban shumën e vlerave të variablave që krijojmë. Më pas krijon një variabël vargu të quajtur label. Ky është një varg i thjeshtë teksti.

Ne do të bëjmë ciklin 10 herë dhe do të krijojmë 10 variabla të quajtur x1 deri në x10. Deklarata eval në trupin e ciklit jep x dhe merr vlerën e numëruesit të ciklit $n për të krijuar emrin e ndryshores. Në të njëjtën kohë, ai vendos variablin e ri në vlerën e numëruesit të ciklit $n.

Ai printon variablin e ri në dritaren e terminalit dhe më pas rrit variablin total me vlerën e ndryshores së re.

Jashtë ciklit, 10 ndryshoret e reja shtypen edhe një herë, të gjitha në një rresht. Vini re se ne mund t'u referohemi gjithashtu variablave me emrat e tyre të vërtetë, pa përdorur një version të llogaritur ose të prejardhur të emrave të tyre.

Së fundi, ne shtypim vlerën e ndryshores total.

./loop.sh

Përdorimi i eval me vargje

Imagjinoni një skenar ku keni një skenar që funksionon gjatë dhe kryen disa përpunime për ju. Ai shkruan në një skedar log me një emër të krijuar nga një vulë kohore. Herë pas here, ai do të fillojë një skedar të ri regjistri. Kur skripti ka përfunduar, nëse nuk ka pasur gabime, ai fshin skedarët e regjistrit që ka krijuar.

Ju nuk dëshironi që ai thjesht të rm *.log, ju dëshironi vetëm të fshijë skedarët e regjistrit që ka krijuar. Ky skript simulon atë funksionalitet. Kjo është clear-logs.sh.

#!/bin/bash

declare -a logfiles

filecount=0 
rm_string="echo"

function create_logfile() {
  ((++filecount))
  filename=$(date +"%Y-%m-%d_%H-%M-%S").log
  logfiles[$filecount]=$filename
  echo $filecount "Created" ${logfiles[$filecount]}
}

# body of the script. Some processing is done here that
# periodically generates a log file. We'll simulate that
create_logfile
sleep 3
create_logfile
sleep 3
create_logfile
sleep 3
create_logfile

# are there any files to remove?
for ((file=1; file<=$filecount; file++))
do
  # remove the logfile
  eval $rm_string ${logfiles[$file]} "deleted..."
  logfiles[$file]=""
done

Skripti deklaron një grup të quajtur logfiles . Kjo do të mbajë emrat e skedarëve të regjistrit që janë krijuar nga skripti. Ai deklaron një variabël të quajtur filecount. Kjo do të mbajë numrin e skedarëve të regjistrit që janë krijuar.

Ai gjithashtu deklaron një varg të quajtur rm_string. Në një skript të botës reale, kjo do të përmbajë komandën rm , por ne po përdorim echo që të mund ta demonstrojmë parimin në një mënyrë jo shkatërruese.

Funksioni create_logfile() është vendi ku emërtohet çdo skedar log dhe ku do të hapet. Ne po krijojmë vetëm emrin e skedarit dhe pretendojmë se është krijuar në sistemin e skedarëve.

Funksioni rrit variablin filecount. Vlera e tij fillestare është zero, kështu që emri i parë i skedarit që krijojmë ruhet në pozicionin një në grup. Kjo është bërë me qëllim, si dhe shih më vonë.

Emri i skedarit krijohet duke përdorur komandën date dhe shtesën .log. Emri ruhet në grup në pozicionin e treguar nga filecount. Emri shtypet në dritaren e terminalit. Në një skenar të botës reale, do të krijoni gjithashtu skedarin aktual.

Trupi i skriptit është simuluar duke përdorur komandën gjumë. Krijon skedarin e parë të regjistrit, pret tre sekonda dhe më pas krijon një tjetër. Ai krijon katër skedarë regjistrash, të ndarë në mënyrë që vulat kohore në emrat e skedarëve të jenë të ndryshëm.

Më në fund, ekziston një lak që fshin skedarët e regjistrit. Skedari numërues i ciklit është vendosur në një. Ai numëron dhe përfshin vlerën e filecount, e cila mban numrin e skedarëve që janë krijuar.

Nëse filecount është vendosur ende në zero - për shkak se nuk janë krijuar skedarë log - trupi i lakut nuk do të ekzekutohet kurrë sepse një nuk është më pak ose e barabartë me zero. Kjo është arsyeja pse ndryshorja filecount u vendos në zero kur u deklarua dhe pse u rrit para krijimit të skedarit të parë.

Brenda ciklit, ne përdorim eval me rm_string tonë jo-shkatërrues dhe emrin e skedarit që merret nga grupi. Më pas vendosim elementin e grupit në një varg bosh.

Kjo është ajo që shohim kur ekzekutojmë skenarin.

./clear-logs.sh

Nuk është gjithçka keq

eval i keqpërdorur padyshim që ka përdorimet e veta. Ashtu si shumica e mjeteve, të përdorura në mënyrë të pamatur është e rrezikshme dhe në më shumë se një mënyrë.

Nëse siguroheni që vargjet në të cilat funksionon janë krijuar nga brenda dhe nuk janë kapur nga njerëzit, API-të ose gjëra të tilla si kërkesat HTTPS, do të shmangni grackat kryesore.