Kodėl OS-COMMAND paslepia „shell“ scenarijų klaidas Progress OpenEdge aplinkoje?
OS-COMMAND paslepia klaidas, nes negrąžina scenarijaus išėjimo kodo (angl. exit code) ir išmeta viską, ką scenarijus įrašo į standartinį klaidų srautą (stderr). Jei scenarijus nutrūksta pusiaukelėje dėl pilno disko ar trūkstamų teisių, ABL tiesiog vykdo kitą kodo eilutę, lyg nieko nebūtų nutikę.
Įprastas iškvietimas atrodo taip:
OS-COMMAND VALUE("myscript.sh").
Išėjimo kodas – tai skaičius, kurį programa grąžina baigusi darbą: 0 reiškia sėkmę, o bet kuris kitas skaičius rodo klaidą. Stderr – tai kanalas, kuriuo dauguma scenarijų siunčia klaidų pranešimus. Be šių dviejų dalykų nepavykusi atsarginė kopija atrodo lygiai taip pat kaip sėkminga. Daugiau apie šio sakinio veikimą rasite Progress Community straipsnyje apie OS-COMMAND, „shell“ scenarijus ir PASOE.
Kaip gauti išėjimo kodus ir klaidas su INPUT THROUGH?
Sprendimas – vietoje OS-COMMAND naudoti INPUT THROUGH, kuris leidžia ABL skaityti komandos išvestį eilutė po eilutės. Kad tai veiktų patikimai, stderr reikia sujungti su įprasta išvestimi, o baigus scenarijų išspausdinti išėjimo kodą pažymėtoje eilutėje. Tada ABL gali perskaityti ir klaidų pranešimus, ir tikrąjį rezultatą.
Progress šį klausimą taip pat aptaria gide, kaip gauti „shell“ scenarijaus grąžinamą reikšmę. Reikia dviejų dalykų:
- Sujungti stderr su standartine išvestimi (stdout) naudojant „shell“ peradresavimą 2>&1, kad klaidų pranešimai nedingtų.
- Iškart po scenarijaus išspausdinti išėjimo kodą su žyme, kurios nebūtų galima supainioti su tikra išvestimi. Taip išėjimo kodas, kurį žino tik „shell“, patenka į srautą, kurį skaito ABL.
Kaip veikia „shell“ komanda?
Visa „shell“ komanda vienoje eilutėje paleidžia scenarijų, peradresuoja jo klaidas ir išspausdina išėjimo kodą:
/bin/sh -c "myscript.sh 2>&1 ; echo @@EXIT_CODE=$?"
- /bin/sh -c "..." paleidžia „shell“ ir viską, kas kabutėse, vykdo kaip vieną komandų eilutę. To reikia, nes 2>&1, ; ir echo yra „shell“ sintaksė, kurios ABL pati nesupranta.
- myscript.sh – paleidžiamas scenarijus.
- 2>&1 sujungia failo deskriptorių 2 (stderr) su failo deskriptoriumi 1 (stdout), todėl klaidų pranešimai patenka į tą patį srautą kaip ir įprasta išvestis.
- ; paleidžia kitą komandą bet kokiu atveju, todėl echo įvykdomas visada, nesvarbu, ar scenarijus pavyko, ar ne.
- echo @@EXIT_CODE=$? baigus scenarijų išspausdina dar vieną eilutę. $? saugo paskutinės komandos išėjimo kodą, o @@EXIT_CODE= yra sugalvota žymė, kuri vargu ar pasitaikys paties scenarijaus išvestyje.
Ką daro pavyzdinis atsarginių kopijų scenarijus?
Pavyzdinis scenarijus sampleScript.sh archyvuoja duomenų katalogą su tar. Jei tar neveikia, scenarijus išspausdina klaidą į stderr ir baigia darbą su kodu 10. Tiktų bet kuris nenulinis skaičius, 10 – tik pavyzdys.
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/opt/app/backups"
SOURCE_DIR="/opt/app/data"
LOGFILE="/var/log/app/backup.log"
mkdir -p "$BACKUP_DIR"
echo "[$(date '+%F %T')] Starting nightly backup of $SOURCE_DIR"
ARCHIVE="$BACKUP_DIR/data-$(date '+%Y%m%d').tar.gz"
if tar -czf "$ARCHIVE" -C "$SOURCE_DIR" . ; then
echo "[$(date '+%F %T')] Archive written to $ARCHIVE"
else
echo "ERROR: tar failed while archiving $SOURCE_DIR" >&2
exit 10
fi
echo "[$(date '+%F %T')] Pruning old archives, keeping 7 copies"
ls -1t "$BACKUP_DIR"/data-*.tar.gz | tail -n +8 | xargs -r rm -f || true
echo "[$(date '+%F %T')] Backup finished successfully" | tee -a "$LOGFILE"
exit 0
Kaip tarpinė procedūra runScript.p nuskaito išvestį?
Procedūra runScript.p gauna komandą, paleidžia ją per INPUT THROUGH ir grąžina išėjimo kodą kartu su visa komandos išvestimi. Jei išėjimo kodas nėra 0, procedūra iškelia ABL klaidą, užuot tyliai baigusi darbą.
DEFINE INPUT PARAMETER pcScript AS CHARACTER NO-UNDO. /* full command */
DEFINE OUTPUT PARAMETER piExitCode AS INTEGER NO-UNDO.
DEFINE OUTPUT PARAMETER pcOutput AS LONGCHAR NO-UNDO.
DEFINE VARIABLE cCmdLine AS CHARACTER NO-UNDO.
DEFINE VARIABLE cLine AS CHARACTER NO-UNDO FORMAT "x(10000)".
DEFINE VARIABLE cSentinel AS CHARACTER NO-UNDO INITIAL "@@EXIT_CODE=".
DEFINE VARIABLE cShellPath AS CHARACTER NO-UNDO.
cShellPath = OS-GETENV("OE_SHELL_PATH").
IF cShellPath = ? OR cShellPath = "" THEN
cShellPath = "/bin/sh".
cCmdLine = cShellPath + ' -c "' + pcScript + ' 2>&1 ; echo ' + cSentinel + '$? ' + '"'.
INPUT THROUGH VALUE(cCmdLine) NO-ECHO UNBUFFERED.
REPEAT ON ENDKEY UNDO, LEAVE:
IMPORT UNFORMATTED cLine.
IF cLine BEGINS cSentinel THEN DO:
piExitCode = INTEGER(SUBSTRING(cLine, LENGTH(cSentinel) + 1)).
NEXT.
END.
pcOutput = pcOutput + cLine + CHR(10).
END.
INPUT CLOSE.
IF piExitCode NE 0 THEN
RETURN ERROR SUBSTITUTE("Shell command failed (exit &1)", piExitCode).
Štai ką daro kiekviena dalis:
- pcScript – paleidžiama komanda, pavyzdžiui, "./sampleScript.sh --mode nightly". piExitCode ir pcOutput užpildomi procedūrai vykstant.
- cShellPath nurodo, kurį „shell“ naudoti. Jei nenurodyta kitaip, naudojamas /bin/sh.
- cCmdLine sudaro visą komandą: <shell> -c "<scenarijus> 2>&1 ; echo <žymė>$?".
- INPUT THROUGH VALUE(cCmdLine) NO-ECHO UNBUFFERED paleidžia komandą ir jos išvestį skaito kaip srautą, panašiai kaip INPUT FROM skaito failą. NO-ECHO neleidžia ABL rodyti išvesties ekrane, o UNBUFFERED kiekvieną eilutę nuskaito vos tik ji atsiranda.
- REPEAT ON ENDKEY UNDO, LEAVE skaito po vieną eilutę ir, pasibaigus įvesčiai, ciklą užbaigia be klaidos.
- IMPORT UNFORMATTED cLine nuskaito lygiai vieną eilutę.
- IF cLine BEGINS cSentinel tikrina, ar tai žymės eilutė. Jei taip, SUBSTRING() paima skaičių po žymės, INTEGER() jį konvertuoja, o rezultatas įrašomas į piExitCode. NEXT praleidžia šią eilutę, todėl žymė niekada nepasiekia kviečiančiosios procedūros.
- pcOutput = pcOutput + cLine + CHR(10) visas kitas eilutes prideda prie išvesties. CHR(10) grąžina eilutės lūžį, kurį pašalina IMPORT UNFORMATTED.
- INPUT CLOSE uždaro srautą, o paskutinis patikrinimas iškelia ABL klaidą, jei išėjimo kodas nėra 0.
Verta atkreipti dėmesį į dvi detales:
- FORMAT "x(10000)" riboja, kiek simbolių nuskaitoma vienoje eilutėje. Padidinkite šią reikšmę, jei tikitės ilgesnių eilučių.
- OE_SHELL_PATH skirtas tik testavimui ne Linux aplinkoje. Gamybinėje aplinkoje jo nenustatykite, kad procedūra naudotų /bin/sh.
Kaip iškviesti tarpinę procedūrą iš ABL?
Procedūra doBackup.p iškviečia runScript.p ir užregistruoja gautą išvestį, nesvarbu, ar scenarijus pavyko, ar ne. Jei išėjimo kodas nėra 0, ji taip pat užregistruoja klaidą ir ją iškelia.
DEFINE VARIABLE iRC AS INTEGER NO-UNDO.
DEFINE VARIABLE lcOut AS LONGCHAR NO-UNDO.
RUN runScript.p (INPUT "./sampleScript.sh --mode nightly", OUTPUT iRC, OUTPUT lcOut).
LOG-MANAGER:WRITE-MESSAGE(STRING(lcOut)).
IF iRC NE 0 THEN
DO:
LOG-MANAGER:WRITE-MESSAGE("Backup FAILED, exit code " + STRING(iRC)).
RETURN ERROR "Nightly backup failed — see log for captured output.".
END.
Testą vykdžiau Git Bash aplinkoje. Pirmoje ekrano kopijoje matyti sėkmingas paleidimas:

Kad sukelčiau klaidą, prieš paleisdamas scenarijų pašalinau šaltinio katalogą:

Naudojant paprastą OS-COMMAND, abu paleidimai būtų atrodę vienodai, o klaida liktų nepastebėta.
Kaip tvarkyti didelius Progress OpenEdge žurnalų failus neapkraunant AVM?
Didelių žurnalų failų neįkelkite į ABL atmintį. Progress OpenEdge žurnalus rotuokite su logrotate ir copytruncate parinktimi, kad nereikėtų perkrauti brokerio, o filtruokite juos operacinės sistemos lygmeniu su awk. Tada ABL gauna tik tas kelias eilutes, kurių jai iš tiesų reikia.
Mažiems failams skaityti su INPUT FROM visiškai tinka. Tačiau kelių gigabaitų žurnalai gali išnaudoti visą atmintį ir sulėtinti kitaip sklandžiai veikiantį duomenų bazės brokerį.
Kodėl OpenEdge brokeriui reikia copytruncate?
Brokeris, t. y. duomenų bazės serverio procesas, laiko savo žurnalo failą atidarytą tol, kol veikia. Jei rotacijos metu įrankis failą pervadina ar ištrina, brokeris toliau rašo į senąjį failą, o naujos žurnalo eilutės nepastebimai dingsta.
logrotate parinktis copytruncate šios problemos išvengia. Ji nukopijuoja dabartinį turinį į naują failą, o originalų failą išvalo vietoje. Brokeris toliau rašo į tą patį failą be perkrovimo, ir nė viena žurnalo eilutė nedingsta. Progress tą pačią problemą aprašo straipsnyje How to truncate UBROKER log files while the Broker is running on UNIX, o Progress dokumentacijoje aprašyta, kaip archyvuoti ir trumpinti duomenų bazės žurnalus.
Kaip sukonfigūruoti logrotate OpenEdge žurnalams?
logrotate – tai nedidelė, dažniausiai iš anksto įdiegta Linux priemonė, kuri pervadina, suspaudžia ir šalina senus žurnalų failus. Ji turi taisykles įprastiems sistemos žurnalams, bet nieko nežino apie /var/log/openedge/. Todėl OpenEdge žurnalai auga tol, kol jiems nesukuriama konfigūracija:
/var/log/openedge/broker.log
/var/log/openedge/client*.log {
daily
rotate 14
maxsize 500M
compress
delaycompress
missingok
notifempty
copytruncate
}
Išsaugokite šį failą kaip /etc/logrotate.d/openedge. Jis nurodo logrotate rotuoti žurnalus kasdien arba anksčiau, jei jie viršija 500 MB, saugoti 14 senų kopijų ir naudoti copytruncate, kad brokerio darbas nebūtų sutrikdytas.
Dvi eilutės prieš { nurodo, kuriems failams taikomos taisyklės. /var/log/openedge/broker.log – tai vienas konkretus failas, o /var/log/openedge/client*.log atitinka client1.log, client2.log ir kitus panašius failus. Taisyklės tarp { } taikomos jiems visiems.
Kaip filtruoti žurnalo klaidas su awk iš ABL?
Procedūra extractLogErrors.p sudaro awk komandą, kuri filtruoja žurnalą pagal datą ir žodį ERROR, o tada paleidžia ją per tą pačią runScript.p procedūrą. Kviečiančioji procedūra visada gauna išėjimo kodą ir atitinkančias eilutes vienodu būdu, nepriklausomai nuo žurnalo failo ar datos.
DEFINE INPUT PARAMETER pcLogFile AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcFilterDate AS CHARACTER NO-UNDO.
DEFINE VARIABLE iRC AS INTEGER NO-UNDO.
DEFINE VARIABLE lcErrors AS LONGCHAR NO-UNDO.
DEFINE VARIABLE cAwkCmd AS CHARACTER NO-UNDO.
cAwkCmd = SUBSTITUTE("awk '/^&1/ &&&& /ERROR/ ~{ print ~}' &2", pcFilterDate, pcLogFile).
RUN runScript.p
( INPUT cAwkCmd
, OUTPUT iRC
, OUTPUT lcErrors ).
IF iRC NE 0 THEN
RETURN ERROR SUBSTITUTE("Log filtering failed (exit &1)", iRC).
IF lcErrors = "" THEN
LOG-MANAGER:WRITE-MESSAGE(SUBSTITUTE("No ERROR entries found for &1 in &2", pcFilterDate, pcLogFile)).
ELSE
LOG-MANAGER:WRITE-MESSAGE(SUBSTITUTE("ERROR entries for &1:~n&2", pcFilterDate, lcErrors)).Štai kaip tai veikia:
- pcLogFile ir pcFilterDate nurodo, kurį žurnalo failą tikrinti ir pagal kurią datą filtruoti, pavyzdžiui, "2026-09-19".
- cAwkCmd iš šablono sudaro awk komandą.
- RUN runScript.p paleidžia komandą. Tarpinei procedūrai nesvarbu, ar ji vykdo awk, ar atsarginių kopijų scenarijų. Išėjimo kodą ji grąžina į iRC, o awk išvestį – į lcErrors.
- Jei iRC nėra 0, pavyzdžiui, dėl klaidingos sintaksės ar trūkstamo failo, procedūra iškelia klaidą. Tai tas pats greito sustojimo principas kaip doBackup.p.
- Tik tada tikrinamas lcErrors. Jei jis tuščias, awk tai datai nieko nerado, todėl užregistruojamas pranešimas „No ERROR entries found“. Priešingu atveju užregistruojamos rastos eilutės.
lcErrors yra bendrosios paskirties išvesties kintamasis, kaip lcOut procedūroje doBackup.p. Jame yra tik ERROR eilutės, nes būtent jas spausdina awk komanda. Rezultatai per LOG-MANAGER įrašomi į OpenEdge kliento žurnalą, todėl šaltinio žurnalo failas niekada nekeičiamas.
Dvi šablono dalys atrodo neįprastai dėl pačios ABL eilučių taisyklių:
- ~{ ~} ekranuoja riestinius skliaustus. Be tildžių ABL preprocesorius { print } skaito kaip nuorodą į įtraukiamą failą.
- &&&& virsta &&. SUBSTITUTE() simbolį & laiko savo vietaženklio (&1, &2) pradžia, todėl awk operatorių && reikia rašyti dukart.
Kaip patikrinti žurnalo filtrą?
Filtrui patikrinti naudojau šį pavyzdinį žurnalą:
2026-09-18 08:12:03 INFO Broker started, listening on port 5162
2026-09-18 09:44:11 INFO Client session 1042 connected
2026-09-18 14:02:57 ERROR Database connection pool exhausted, retrying
2026-09-18 22:10:45 INFO Nightly batch job completed
2026-09-19 00:01:02 INFO Broker health check OK
2026-09-19 03:15:29 ERROR Disk usage on /db01 exceeded 90% threshold
2026-09-19 07:30:10 INFO Client session 1043 connected
2026-09-19 07:45:22 ERROR Timeout waiting for record lock on customer table
2026-09-19 12:00:00 INFO Broker health check OK
2026-09-19 18:20:38 ERROR Backup script exited with non-zero status
Testas vykdomas procedūroje doLogCheck.p. Tai paprasta kviečiančioji procedūra, sukurta tuo pačiu principu kaip doBackup.p. Ji per SESSION:PARAMETER nuskaito datą, perduotą paleidimo parinktimi -param. Jei data neperduota, naudojama šiandienos data YYYY-MM-DD formatu. Šis formatas atitinka žurnalo laiko žymas, nes awk šablonas ieško datos kiekvienos eilutės pradžioje.
DEFINE VARIABLE cDate AS CHARACTER NO-UNDO.
cDate = SESSION:PARAMETER.
IF cDate = ? OR cDate = "" THEN
cDate = STRING(YEAR(TODAY), "9999") + "-" + STRING(MONTH(TODAY), "99") + "-" + STRING(DAY(TODAY), "99").
RUN extractLogErrors.p (INPUT "sampleAppLog.log", INPUT cDate).
Rezultatas atrodo taip:

Labai didelių failų, pavyzdžiui, dešimčių gigabaitų žurnalų, neapdorokite iš brokerio ar sesijos, aptarnaujančios aktyvius naudotojus. Paleiskite procesą kaip atskirą paketinę užduotį, kuri rezultatus įrašo ten, kur ABL galės juos vėliau perskaityti. Taip laukimas, kol awk baigs darbą, nestabdys nieko, kas matoma naudotojams.
Kaip įdiegti šį sprendimą Progress OpenEdge aplinkoje?
Sprendimą įdiegsite per šešis žingsnius: padarykite scenarijų vykdomą, iškvieskite jį keliu, kurį „shell“ randa, įdiekite ABL tarpinę procedūrą, įjunkite OpenEdge kliento žurnalą, įdiekite logrotate konfigūraciją bei patikrinkite ir sėkmingą, ir nesėkmingą scenarijų.
1. Padarykite scenarijų vykdomą ir kontroliuokite, kas jį gali paleisti.
Paleiskite chmod +x sampleScript.sh, nes nevykdomas failas nepasileis. Apribokite teises taip, kad scenarijų tiesiogiai galėtų paleisti tik OpenEdge paskyra, o visi kiti naudotų ABL tarpinę procedūrą. Taip registruojami ir jų paleidimai.
2. Naudokite kelią, kurį „shell“ gali rasti.
Vien failo pavadinimas, pavyzdžiui, sampleScript.sh, veikia tik tada, jei jo katalogas yra PATH kintamajame. Naudokite ./sampleScript.sh, jei failas tame pačiame kataloge, arba pilną kelią.
3. Paruoškite ABL dalį.
runScript.p padėkite ten, iš kur ją gali iškviesti kiti .p failai, ir kvieskite ją taip pat, kaip tai daro doBackup.p ir extractLogErrors.p.
4. Įjunkite OpenEdge kliento žurnalą.
Prieš kviesdami LOG-MANAGER:WRITE-MESSAGE, įjunkite žurnalą paleidimo parinktimi -clientlog arba kode nustatydami LOG-MANAGER:LOGFILE-NAME. Kitaip pranešimai neturės kur būti įrašyti.
5. Įdiekite logrotate konfigūraciją.
Padėkite ją Linux serverio kataloge /etc/logrotate.d/. Prieš ja pasitikėdami, paleiskite logrotate -d /etc/logrotate.d/openedge. Parinktis -d parodo, kas įvyktų, nekeisdama jokių failų.
6. Patikrinkite abu rezultatus.
Paleiskite užduotį, kai viskas veikia, o tada tyčia ją sugadinkite, pavyzdžiui, nukreipkite atsarginių kopijų scenarijų į neegzistuojantį katalogą. Įsitikinkite, kad išėjimo kodas ir klaidos tekstas atsiranda žurnale. Kol nematėte užfiksuotos klaidos, klaidų apdorojimas nėra patikrintas.
Su kokiais iššūkiais galite susidurti?
Dauguma problemų kyla dėl ABL eilučių apdorojimo, duomenų tipų apribojimų ir aplinkų skirtumų. Lentelėje pateikiamos testuojant pastebėtos problemos ir jų sprendimai.
Ką parodė testai?
Abu sprendimai veikė taip, kaip numatyta. „Shell“ o logika testuota Git Bash aplinkoje Windows sistemoje, o ne Linux serveryje. Git Bash turi tikrą POSIX „shell“ (/bin/sh, awk, chmod), todėl „shell“ ir awk logika veikia taip pat kaip Linux. ABL kodas vykdytas tikroje AVM.
- Pirmasis atvejis: sėkmingas paleidimas grąžino iRC = 0 su visa išvestimi. Tyčia sukelta klaida grąžino iRC = 10 kartu su tikra tar klaidos eilute iš stderr.
- Antrasis atvejis: awk filtras iš sampleAppLog.log grąžino lygiai tris ERROR eilutes 2026-09-19 datai, o datai be atitikmenų teisingai negrąžino nieko.
- runScript.p: ištaisius LONGCHAR ir IMPORT UNFORMATTED problemas, procedūra tikroje AVM susikompiliavo ir veikė be klaidų. Tai patvirtina, kad išėjimo kodo gavimas veikia ABL lygmeniu, nesvarbu, kokią komandą vykdo tarpinė procedūra.
Svarbiausios įžvalgos apie „shell“ įrankių naudojimą Progress OpenEdge aplinkoje
„Shell“ kvietimas iš Progress OpenEdge nebūtinai reiškia matomumo praradimą. Paleidus scenarijus per INPUT THROUGH su sujungtu stderr ir žymės eilute, tylios klaidos virsta išėjimo kodais ir klaidų tekstu, kuriuos ABL gali patikrinti ir užregistruoti. Perkėlus žurnalų filtravimą į awk, o rotaciją į logrotate su copytruncate, dideli failai lieka už AVM atminties ribų, o brokeris veikia be pertraukų.
Šie sprendimai išsprendžia tris praktines problemas:
- Tylios klaidos tampa matomos. ABL požiūriu OS-COMMAND visada atrodo sėkmingas. INPUT THROUGH su žymės eilute suteikia ABL tikrą išėjimo kodą ir klaidos tekstą.
- Žurnalo dydis nebėra ABL atminties problema. Filtruojama su awk už AVM ribų, o ABL gauna tik atitinkančias eilutes.
- Rotacijai nebereikia perkrauti brokerio. copytruncate išvalo žurnalo failą vietoje, todėl brokerio atidarytas failas išlieka galiojantis.
Jei planuojate padidinti savo Progress OpenEdge programų patikimumą arba jums reikia pagalbos tvarkant scenarijus ir žurnalus, mūsų Progress OpenEdge komanda pasiruošusi padėti. Užsiregistruokite konsultacijai su mūsų specialistais ir aptarkime jūsų sprendimą.




