PHP CLI में सिस्टम यूटिलिटीज लिखना

अधिकांश पेशेवरों के लिए, PHP एक ऐसी भाषा नहीं है जिसका उपयोग कंसोल उपयोगिताओं को लिखने के लिए गंभीरता से किया जाएगा, और इसके कई कारण हैं। PHP को मूल रूप से वेबसाइट बनाने के लिए एक भाषा के रूप में विकसित किया गया था, लेकिन, PHP 4.3 से शुरू होकर, 2002 में CLI मोड के लिए आधिकारिक समर्थन था, इसलिए ऐसा होना बंद हो गया। कई वर्षों के दौरान, PHP में कई डिवेलपर्स इंटरएक्टिव सीएलआई उपयोगिताओं का सफलतापूर्वक उपयोग कर रहे हैं।

इस लेख में, हम अपने अनुभव को PHP में CLI मोड के साथ साझा करना चाहते हैं और कुछ सिफारिशें उन लोगों को देना चाहते हैं जो PHP में स्क्रिप्ट लिखने जा रहे हैं, बशर्ते कि वे एक * nix सिस्टम में चलेंगे (हालांकि, लगभग सब कुछ विंडोज के लिए भी सही है )।

सिफारिशें


काम की गति

यह व्यापक रूप से माना जाता है कि PHP एक धीमी भाषा है, और यह वास्तव में है। PHP CLI के लिए, दो कारणों से भारी फ्रेमवर्क और यहां तक ​​कि सिर्फ बड़े PHP पुस्तकालयों का उपयोग न करने की सिफारिश की जाती है:
  1. सीएलआई मोड में शामिल / आवश्यकता के रन समय में हमेशा पार्सिंग और निष्पादन शामिल होगा, जैसा कि इस मोड में बाइटकोड को कैश नहीं किया गया है (कम से कम डिफ़ॉल्ट रूप से), जिसका अर्थ है कि आरंभीकरण में बहुत समय लगेगा, भले ही सब कुछ वेब सर्वर के तहत बहुत जल्दी से काम करता हो।
  2. किसी पृष्ठ को लोड करने के लिए वेब साइट उपयोगकर्ताओं को एक निश्चित समय की प्रतीक्षा करने के लिए उपयोग किया जाता है (लगभग 1 सेकंड, और कभी-कभी थोड़ा अधिक, उपयोगकर्ता इसे सामान्य रूप से मानता है), लेकिन सीएलआई के बारे में भी ऐसा नहीं कहा जा सकता है: यहां तक ​​कि 100 एमएस की देरी पहले से ही ध्यान देने योग्य होगी, और 1 सेकंड में या अधिक कष्टप्रद हो सकता है।
स्क्रीन आउटपुट

सीएलआई और वेब मोड में, स्क्रीन पर आउटपुट काफी भिन्न होता है। वेब मोड में, आउटपुट आमतौर पर बफ़र किया जाता है; स्क्रिप्ट निष्पादित करते समय आप उपयोगकर्ता से कुछ भी नहीं पूछ सकते हैं; एक वर्ग के रूप में गायब होना त्रुटि स्ट्रीम में आउटपुट की अवधारणा है। सीएलआई मोड में, स्वाभाविक रूप से, HTML आउटपुट अस्वीकार्य है, और लंबी लाइनों का उत्पादन अत्यधिक अवांछनीय है। सीएलआई में, डिफ़ॉल्ट कॉल फ्लश द्वारा गूंज () (अधिक विवरण यहां पाया जा सकता है ) - यह सुविधाजनक है क्योंकि आपको मैन्युअल रूप से फ्लश () को कॉल करने के बारे में चिंता करने की ज़रूरत नहीं है, उदाहरण के लिए, आउटपुट को फ़ाइल पर रीडायरेक्ट किया जाता है।

CLI लिपियों के लिए, यह STDOUT में नहीं (ईको का उपयोग करते हुए) आउटपुट त्रुटियों के लिए समझ में आता है, लेकिन STDERR में: इस तरह, भले ही प्रोग्राम आउटपुट को कहीं और रीडायरेक्ट किया गया हो (उदाहरण के लिए, / dev / null या grep के लिए), उपयोगकर्ता पाठ को याद नहीं करेगा। इसकी घटना के मामले में त्रुटियाँ। यह अधिकांश देशी * निक्स कंसोल उपयोगिताओं के लिए मानक व्यवहार है, और एसटीडीआरआर ऊपर वर्णित कारण के लिए मौजूद है। PHP में, STDERR को लिखने के लिए आप उपयोग कर सकते हैं, उदाहरण के लिए, fwrite (STDERR, $ संदेश) या error_log ($ संदेश)।

रिटर्न कोड का उपयोग करना

वापसी कोड एक संख्या है जो 0 है यदि कमांड सफल होता है और 0 नहीं तो। 1 का एक रिटर्न कोड अक्सर गैर-महत्वपूर्ण त्रुटियों के मामले में उपयोग किया जाता है (उदाहरण के लिए, यदि गलत कमांड लाइन तर्क निर्दिष्ट किए जाते हैं), और 2 - महत्वपूर्ण सिस्टम त्रुटियों के मामले में (उदाहरण के लिए, यदि नेटवर्क या डिस्क त्रुटि)। 127 या 255 जैसे मूल्यों का उपयोग आमतौर पर किसी भी विशेष मामलों के लिए किया जाता है जो प्रलेखन में अलग से दिखाए जाते हैं।

डिफ़ॉल्ट रूप से, जब आप बस एक PHP स्क्रिप्ट को समाप्त करते हैं, तो यह माना जाता है कि सभी कमांड सफलतापूर्वक काम करते हैं और 0. वापस आते हैं। एक विशिष्ट रिटर्न कोड के साथ बाहर निकलने के लिए, आपको स्पष्ट रूप से निकास (NUM) कॉल करना होगा, जहां NUM रिटर्न कोड है (याद रखें कि यह 0 में है। सफलता और त्रुटियों के मामले में एक अलग अर्थ है)।

यह समझने के लिए कि निष्पादन () या सिस्टम () के साथ निष्पादित एक बाहरी कमांड सफल नहीं हुई, आपको चर $ return_var को संबंधित कार्यों के मापदंडों के रूप में पास करने और शून्य के लिए समानता के लिए मूल्य की जांच करने की आवश्यकता है।

चेतावनी! यदि आप निष्पादन ('some_cmd ... 2> और 1', $ आउटपुट) लिखने जा रहे हैं, ताकि त्रुटियाँ भी $ आउटपुट में आए, तो हम अनुशंसा करते हैं कि आप STDOUT और STDERR को अलग करने के कारणों से परिचित हों और STDOUT (2> & 1) में त्रुटि स्ट्रीम के स्पष्ट पुनर्निर्देशन को हटा दें। इस तरह के पुनर्निर्देशन की आवश्यकता बहुत कम बार की तुलना में यह लग सकता है। एकमात्र मामला जब इसका उपयोग थोड़ा न्यायसंगत होता है (एक PHP स्क्रिप्ट में) एक वेब पेज पर कमांड निष्पादन के परिणाम को प्रिंट करने की आवश्यकता होती है (सीएलआई में नहीं!), इसमें त्रुटियां शामिल हैं (अन्यथा वे वेब सर्वर के लॉग में समाप्त हो जाएंगे या यहां तक ​​कि जा सकते हैं) / देव / नल)।

सिस्टम के अंतर्निहित कमांड के तहत "भेस"

एक अच्छी कंसोल उपयोगिता को एक मानक तरीके से व्यवहार करना चाहिए और उपयोगकर्ताओं को यह भी पता नहीं चल सकता है कि यह PHP में है। ऐसा करने के लिए, * निक्स-सिस्टम एक ऐसा तंत्र प्रदान करता है जो पर्ल / पायथन / रूबी में स्क्रिप्ट चलाने के लिए बहुत से जाना जाता है, लेकिन समान रूप से PHP के लिए लागू होता है।

यदि आप उदाहरण के लिए, #! / Usr / bin / env php को PHP फ़ाइल और लाइन ब्रेक की शुरुआत में जोड़ते हैं, तो इसे निष्पादित अधिकार (chmod 755 myscript.php) दें और .php एक्सटेंशन को हटा दें (बाद वाला वैकल्पिक है), तो फ़ाइल हो सकती है। निष्पादित करेगा, किसी भी अन्य निष्पादन योग्य (./myscript) की तरह। आप निर्देशिका को PATH में स्क्रिप्ट के साथ जोड़ सकते हैं या इसे मानक PATH निर्देशिकाओं में से एक में स्थानांतरित कर सकते हैं, उदाहरण के लिए, / usr / स्थानीय / बिन, और फिर स्क्रिप्ट को किसी भी अन्य सिस्टम उपयोगिता की तरह "myscript" के एक साधारण सेट के साथ कहा जा सकता है।

हैंडलिंग कमांड लाइन तर्क

कमांड लाइन तर्कों के प्रारूप पर एक समझौता है जो अधिकांश अंतर्निहित सिस्टम उपयोगिताओं का पालन करता है, और हम अनुशंसा करते हैं कि आप इसे और आपकी स्क्रिप्ट का पालन करें।

अपनी स्क्रिप्ट के लिए एक संक्षिप्त सहायता लिखें यदि उसे गलत संख्या में तर्क मिले।

तथाकथित स्क्रिप्ट का नाम जानने के लिए, $ argv का उपयोग करें [0]:

if($argc != 2) { //   \n   echo "Usage: ".$argv[0]." <filename>\n"; //    ,     exit(1); } 

ध्वज हैंडलिंग की सुविधा के लिए, आप गेटटॉप () का उपयोग कर सकते हैं। Getopt () कमांड लाइन तर्क के प्रसंस्करण के लिए अंतर्निहित कार्यों में से एक है। दूसरी ओर, तर्कों के भाग को मैन्युअल रूप से संसाधित करना मुश्किल नहीं है, जैसा कि PHP में यह मुश्किल नहीं है। यह विधि आवश्यक हो सकती है यदि आपको ssh या sudo की शैली में तर्क संसाधित करने की आवश्यकता होती है (sudo -u none echo Hello world निष्पादित करेगा गूंज नमस्ते दुनिया उपयोगकर्ता से कोई नहीं, जो -u ध्वज के बाद निर्दिष्ट होता है)।

अधिक कठिन स्तर के लिए सिफारिशें


सीएलआई के लिए "सही" प्रणाली () को कॉल करना

सिस्टम का कार्यान्वयन () पहले ही यहां लिखा जा चुका है । मुद्दा यह है कि PHP में मानक प्रणाली () C में सिस्टम () के लिए कॉल नहीं है, लेकिन पॉपेन () पर एक आवरण, क्रमशः, "स्क्रिप्ट" को "खराब" करता है और कॉल की गई स्क्रिप्ट के बारे में। इसे होने से रोकने के लिए, आपको निम्न फ़ंक्शन का उपयोग करने की आवश्यकता है:

 //      system()   function cSystem($cmd) { $pp = proc_open($cmd, array(STDIN,STDOUT,STDERR), $pipes); if(!$pp) return 127; return proc_close($pp); } 


फ़ाइल सिस्टम के साथ काम करें

संभावित आश्चर्य के लिए, हम फ़ाइलों के पुनरावर्ती विलोपन (कॉपी करना, हिलना) के अपने स्वयं के कार्यान्वयन को नहीं लिखने की सलाह देते हैं, लेकिन इसके बजाय अंतर्निहित mv, rm, cp कमांड (विंडोज के लिए संगत एनालॉग्स) का उपयोग करें। यह विंडोज / * निक्स के बीच पोर्टेबल नहीं है, लेकिन यह नीचे वर्णित कुछ समस्याओं से बचा जाता है।

आइए PHP में एक पुनरावर्ती निर्देशिका विलोपन को लागू करने का एक सरल उदाहरण देखें:

 //  !  rm -r function recursiveDelete($path) { if(is_file($path)) return unlink($path); $dh = opendir($path); while(false !== ($file = readdir($dh))) { if($file != '.' && $file != '..') recursiveDelete($path.'/'.$file); } closedir($dh); return rmdir($path); } 


पहली नज़र में, यह सही है, सही है? इसके अलावा, यहां तक ​​कि PHP में प्रसिद्ध फ़ाइल प्रबंधकों (उदाहरण के लिए, eXtplorer और प्रलेखन में टिप्पणियों में), एक फ़ोल्डर को हटाना इस तरह से लागू किया जाता है। अब एक nonexistent फ़ाइल (ln -s some_test other_test) के लिए एक प्रतीकात्मक लिंक बनाएं और इसे हटाने का प्रयास करें। या अपने आप को फ़ोल्डर में एक प्रतीकात्मक लिंक बनाएं, या एफएस की जड़ में (हम इस विकल्प का परीक्षण नहीं करने की सलाह देते हैं) ... विशेष रूप से पुनरावर्ती के लिए (), निश्चित रूप से, तुच्छ है, लेकिन यह स्पष्ट है कि यह पहिया को सुदृढ़ करने और अंतर्निहित आदेशों का उपयोग करने के लिए बेहतर नहीं है, हालांकि थोड़ा सा खोने पर। प्रदर्शन में।

सफाई में त्रुटि

यदि आपकी स्क्रिप्ट फ़ाइलों (डेटाबेस, सॉकेट्स इत्यादि) के साथ कुछ ऑपरेशन करती है, तो आपको अक्सर अप्रत्याशित त्रुटियों के मामले में प्रोग्राम को सही ढंग से बंद करने की आवश्यकता होती है: यह लॉग को लिख सकता है, अस्थायी फ़ाइलों को साफ़ कर सकता है, फ़ाइल लॉक हटा सकता है, आदि। .D।

PHP वेब मोड में, इसे register_shutdown_function () का उपयोग करके कार्यान्वित किया जाता है, जो तब भी ट्रिगर होता है जब स्क्रिप्ट एक घातक त्रुटि के साथ समाप्त होती है (इस विधि, स्मृति की कमी त्रुटियों सहित लगभग किसी भी त्रुटि को पकड़ने के लिए उपयुक्त है)। सीएलआई मोड में, सब कुछ थोड़ा अधिक जटिल है, क्योंकि उपयोगकर्ता, उदाहरण के लिए, आपकी स्क्रिप्ट Ctrl + C भेज सकता है, और register_shutdown_function () काम नहीं करेगा।

लेकिन स्पष्टीकरण सरल है: डिफ़ॉल्ट रूप से PHP UNIX संकेतों को बिल्कुल भी संसाधित नहीं करता है, इसलिए किसी भी संकेत को तुरंत प्राप्त करने से स्क्रिप्ट समाप्त हो जाती है। यह घोषित करने के बाद फ़ाइल के शीर्ष पर डिक्लेयर (टिक = 1) जोड़कर तय किया जा सकता है। हमारे लिए ब्याज के संकेतों के लिए अपने हैंडलर को पंजीकृत करना और ( यहां और अधिक विस्तार से):

 pcntl_signal(SIGINT, function() { exit(1); }); // Ctrl+C pcntl_signal(SIGTERM, function() { exit(1); }); // killall myscript / kill <PID> pcntl_signal(SIGHUP, function() { exit(1); }); //   

सिग्नल प्रोसेसिंग के कार्य सभी के लिए समान नहीं होते हैं। आप सिग्नल हैंडलर के अंदर एक्ज़िट () को कॉल नहीं कर सकते हैं - फिर सिग्नल को संसाधित करने के बाद स्क्रिप्ट निष्पादित करना जारी रखेगा।

कई प्रक्रियाओं में डेटाबेस के साथ काम करना (कांटा के बाद)

सिफारिश बहुत सरल है: आपको फोर्क को निष्पादित करने से पहले डेटाबेस से सभी कनेक्शन बंद करना चाहिए () (आदर्श रूप से, यहां तक ​​कि फ़ॉपन के साथ खुली हुई फ़ाइलें) (मौजूद नहीं होनी चाहिए), क्योंकि इन मामलों में निष्पादन कांटा () बहुत ही अजीब परिणाम दे सकता है, और डेटाबेस से जुड़ने के लिए यह किसी भी कांटे की प्रक्रिया के पूरा होने के बाद कनेक्शन को बंद करने की ओर ले जाएगा। वही SQLite मैनुअल स्पष्ट रूप से बताता है कि कांटा () से पहले खोले गए संसाधन का उपयोग कांटे की प्रक्रियाओं में नहीं किया जा सकता है क्योंकि यह इस तरह से मल्टीथ्रेडेड एक्सेस का समर्थन नहीं करता है। किसी भी स्थिति में, PHP में pcntl_fork () केवल कांटा बनाता है () और त्रुटियों को लॉग करता है, इसलिए आपको इसे सी के रूप में सावधानी से संभालने की आवश्यकता है।

जटिल प्रतिपादन के लिए ncurses का उपयोग करना

Ncurses पुस्तकालय विशेष रूप से बनाया गया था ताकि आपको टर्मिनल में कर्सर की स्थिति को नियंत्रित करने के लिए esc दृश्यों के बारे में चिंता करने की ज़रूरत न हो और यह कि एक प्रोग्राम जो उपयोग करता है, उदाहरण के लिए, रंग, सिस्टम और टर्मिनलों के बीच पोर्टेबल है। दूसरी ओर, रंग आउटपुट जैसी सरल चीजों के लिए भी, आपको यह ध्यान रखना होगा कि STDOUT हमेशा रंगों का समर्थन नहीं करता है। हम एक आदिम, लेकिन अविश्वसनीय, बिना नर्सों के पता लगाने का तरीका जानते हैं कि क्या टर्मिनल रंग का समर्थन करता है - यह जांचने के लिए कि क्या STDOUT टर्मिनल है (पॉज़िक्स_आईसैट्टी (1))।

स्क्रीन पर प्रदर्शित संख्या

अधिकांश मानक कार्यक्रम स्क्रीन पर लगभग कुछ भी प्रदर्शित नहीं करते हैं, जब तक कि वे विशेष रूप से -v स्विच (क्रिया, गपशप) निर्दिष्ट करके इसके लिए नहीं पूछते हैं। वास्तव में, बिना किसी कारण के स्क्रीन को रोकना मत। एक संतुलन खोजना मुश्किल हो सकता है, लेकिन कुछ सरल सिफारिशें हैं:
  1. यदि ऑपरेशन में बहुत समय नहीं लगता है (10 सेकंड से कम), तो कुछ भी प्रिंट न करें;
  2. यदि आप कुछ गैर-तुच्छ काम कर रहे हैं (उदाहरण के लिए, sudo का उपयोग करके अस्थायी उपकरणों को बढ़ाना), इसके विपरीत, उपयोगकर्ता को इस बारे में सूचित करें ताकि वह जानता है कि त्रुटि के मामले में उसे क्या करना है;
  3. यदि ऑपरेशन लंबा है और इसके लिए प्रगति दिखाना संभव है, तो यह बहुत प्रगति दिखाने के लिए बेहतर है (इसके लिए, ऊपर उल्लेखित सिस्टम फ़ंक्शन उपयोगी हो सकता है);
  4. यदि प्रोग्राम फ़िल्टर के रूप में काम कर सकता है (उदाहरण के लिए, बिल्ली, grep, gzip ...), तो जांच लें कि केवल डेटा STDOUT में है, और त्रुटियों, इनपुट के लिए संकेत आदि, STDERR पर जाएं ताकि श्रृंखला में अगले प्रोग्राम को कोई भी प्राप्त न हो। अनावश्यक कचरा।
प्रगति दिखाने के लिए, आप कर सकते हैं जैसा कि गिट करता है: इस धारणा का उपयोग करें कि सभी टर्मिनलों की चौड़ाई कम से कम 80 वर्ण है, और निश्चित चौड़ाई की एक पंक्ति प्रिंट करें। यह देखते हुए कि कैरिज रिटर्न कैरेक्टर (\ r) कर्सर को लाइन की शुरुआत में लौटाता है (और अगला आउटपुट पहले जो लाइन पर था, उसे ओवरराइट करता है), कोड लिखने में बहुत आसान है जो प्रदर्शित करता है, उदाहरण के लिए, ऑपरेशन का प्रतिशत 0 से 100 तक। एक ही समय में, उपयोगकर्ता की स्क्रीन पर केवल एक लाइन लेना:

 for($i = 0; $i <= 100; $i++) { printf("\r%3d%%", $i); sleep(1); } echo "\n"; 


स्क्रिप्ट को कॉल करने वाले उपयोगकर्ता का नाम परिभाषित करना

उपयोगकर्ता नाम USER परिवेश चर ($ _ENV ['USER') में समाहित है, लेकिन एक पकड़ है - यह विधि पर्यावरण चर का उपयोग करती है जो गलत डेटा की सूचना दे सकती है (उपयोगकर्ता किसी स्क्रिप्ट को निष्पादित कर सकता है, जैसे USER = रूट स्क्रिप्ट, और स्क्रिप्ट इच्छाशक्ति मान लें कि उपयोगकर्ता नाम रूट है)।

इसलिए, आपको सकारात्मक कार्यों का उपयोग करने की आवश्यकता है:

 // getuid()  ,   ,    uid –        $info = posix_getpwuid(posix_getuid()); $login = $info['name']; 


निष्कर्ष


इस लेख में, हमने उन अनुशंसाओं को प्रदान करने का प्रयास किया जो सामान्य तौर पर कंसोल उपयोगिताओं को लिखने वाले सभी प्रोग्रामर की तुलना में सीधे PHP डेवलपर्स के लिए स्पष्ट नहीं हैं। यद्यपि उपरोक्त में से अधिकांश को अन्य प्रोग्रामिंग भाषाओं पर लागू किया जा सकता है, और शायद कुछ बिंदु उन लोगों के लिए उपयोगी होंगे जो PHP में लिखने नहीं जा रहे हैं।

Yuri youROCK Nasretdinov , Badoo डेवलपर

Source: https://habr.com/ru/post/In136846/


All Articles