रूबी न केवल एक मजेदार भाषा है, यह एक उत्कृष्ट मानक पुस्तकालय के साथ भी आती है। जिनमें से कुछ ज्ञात नहीं हैं, और लगभग छिपे हुए रत्न हैं। आज अतिथि लेखक माइकल कोल ने एक पसंदीदा:स्ट्रिंगस्कैनर पर प्रकाश डाला।
रूबी के छिपे हुए रत्न:StringScanner
ओपनस्ट्रक्चर और सेट ओवर सीएसवी पार्सिंग से बेंचमार्किंग जैसे डेटा संरचनाओं से, तीसरे पक्ष के रत्नों को स्थापित करने का सहारा लिए बिना कोई बहुत दूर जा सकता है। हालांकि, रूबी की मानक स्थापना में कुछ कम प्रसिद्ध पुस्तकालय उपलब्ध हैं जो बहुत उपयोगी हो सकते हैं, जिनमें से एक है StringScanner
जो प्रलेखन के अनुसार "एक स्ट्रिंग पर शाब्दिक स्कैनिंग संचालन प्रदान करता है" ।
स्कैन करना और पार्स करना
तो "लेक्सिकल स्कैनिंग" का वास्तव में क्या मतलब है? अनिवार्य रूप से यह कुछ नियमों का पालन करते हुए इनपुट स्ट्रिंग लेने और उसमें से सार्थक बिट्स निकालने की प्रक्रिया का वर्णन करता है। उदाहरण के लिए, यह एक कंपाइलर के पहले चरण में देखा जा सकता है जो 2 + 1
जैसे एक्सप्रेशन लेता है। इनपुट के रूप में और इसे टोकन के निम्नलिखित अनुक्रम में बदल देता है:
[{ number: "1" }, {operator: "+"}, { number: "1"}]
लेक्सिकल स्कैनर आमतौर पर परिमित-राज्य ऑटोमेटा के रूप में कार्यान्वित किए जाते हैं और कई प्रसिद्ध उपकरण उपलब्ध हैं जो उन्हें हमारे लिए उत्पन्न कर सकते हैं (जैसे एएनटीएलआर या रैगेल)।
हालांकि, कभी-कभी हमारी पार्सिंग की जरूरत इतनी विस्तृत नहीं होती है, और नियमित अभिव्यक्ति आधारित StringScanner
जैसी सरल लाइब्रेरी होती है। ऐसी स्थितियों में बहुत काम आ सकता है। यह तथाकथित स्कैन पॉइंटर . के स्थान को याद करके काम करता है जो स्ट्रिंग में एक इंडेक्स से ज्यादा कुछ नहीं है। स्कैनिंग प्रक्रिया तब प्रदान की गई अभिव्यक्ति के साथ स्कैन पॉइंटर के ठीक बाद कोड से मिलान करने का प्रयास करती है। मिलान संचालन के अलावा, StringScanner
स्कैन पॉइंटर को स्थानांतरित करने के तरीके भी प्रदान करता है (स्ट्रिंग के माध्यम से आगे या पीछे की ओर बढ़ना), आगे देखना (यह देखना कि स्कैन पॉइंटर को अभी तक संशोधित किए बिना आगे क्या है) और साथ ही यह पता लगाना कि स्ट्रिंग में हम वर्तमान में कहां हैं (क्या यह शुरुआत है या एक पंक्ति का अंत/पूरी स्ट्रिंग आदि)।
रेल लॉग पार्स करना
पर्याप्त सिद्धांत, आइए देखें StringScanner
कार्रवाई में। निम्नलिखित उदाहरण नीचे की तरह एक रेल की लॉग प्रविष्टि लेगा,
log_entry = <<EOS
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
Processing by HomeController#index as HTML
Rendered text template within layouts/application (0.0ms)
Rendered layouts/_assets.html.erb (2.0ms)
Rendered layouts/_top.html.erb (2.6ms)
Rendered layouts/_about.html.erb (0.3ms)
Rendered layouts/_google_analytics.html.erb (0.4ms)
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
EOS
और इसे निम्नलिखित हैश में पार्स करें:
{
method: "GET",
path: "/"
ip: "127.0.0.1",
timestamp: "2017-08-20 20:53:10 +0900",
success: true,
response_code: "200",
duration: "79ms",
}
NB:हालांकि यह StringScanner
. के लिए एक अच्छा उदाहरण है लॉगरेज और उसके JSON लॉग फॉर्मेटर का उपयोग करके एक वास्तविक एप्लिकेशन बेहतर होगा।
StringScanner
use का उपयोग करने के लिए हमें पहले इसकी आवश्यकता है:
require 'strscan'
इसके बाद हम कंस्ट्रक्टर को एक तर्क के रूप में लॉग एंट्री पास करके एक नया इंस्टेंस इनिशियलाइज़ कर सकते हैं। साथ ही हम अपने पार्सिंग प्रयासों का परिणाम रखने के लिए एक खाली हैश भी परिभाषित करेंगे:
scanner = StringScanner.new(log_entry)
log = {}
अब हम अपने स्कैन पॉइंटर का वर्तमान स्थान प्राप्त करने के लिए स्कैनर की पॉज़ विधि का उपयोग कर सकते हैं। जैसा अपेक्षित था, परिणाम 0
. है , स्ट्रिंग का पहला अक्षर:
scanner.pos #=> 0
आइए इसकी कल्पना करें ताकि इस प्रक्रिया का अनुसरण करना आसान हो जाए:
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
स्कैनर की स्थिति के और आत्मनिरीक्षण के लिए हम beginning_of_line?
का उपयोग कर सकते हैं और eos?
यह पुष्टि करने के लिए कि स्कैन पॉइंटर वर्तमान में एक पंक्ति की शुरुआत में है और हमने अभी तक अपने इनपुट का पूरी तरह से उपभोग नहीं किया है:
scanner.beginning_of_line? #=> true
scanner.eos? #=> false
पहली जानकारी जिसे हम निकालना चाहते हैं, वह HTTP अनुरोध विधि है, जिसे "प्रारंभ" शब्द के ठीक बाद एक स्थान के बाद पाया जा सकता है। हम स्कैन पॉइंटर को आगे बढ़ाने के लिए स्कैनर की उचित नामित स्किप विधि का उपयोग कर सकते हैं, जो अनदेखा वर्णों की संख्या लौटाएगा, जो हमारे मामले में 8 है। इसके अतिरिक्त हम मिलान का उपयोग कर सकते हैं? यह पुष्टि करने के लिए कि सब कुछ अपेक्षित रूप से काम कर रहा है:
scanner.skip(/Started /) #=> 8
scanner.matched? #=> true
स्कैन पॉइंटर अब अनुरोध विधि के ठीक पहले है:
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
अब हम वास्तविक मान निकालने के लिए scan_until का उपयोग कर सकते हैं, जो संपूर्ण नियमित अभिव्यक्ति मिलान देता है। चूंकि अनुरोध विधि सभी अपरकेस में है, हम एक साधारण वर्ण वर्ग और +
. का उपयोग कर सकते हैं ऑपरेटर जो एक या वर्णों से मेल खाता है:
log[:method] = scanner.scan_until(/[A-Z]+/) #=> "GET"
इस ऑपरेशन के बाद स्कैन पॉइंटर शब्द "GET" के अंतिम "T" पर होगा।
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
अनुरोधित पथ को निकालने के लिए, इसलिए हमें एक स्थान छोड़ना होगा और फिर दोहरे उद्धरण चिह्नों में संलग्न सब कुछ निकालना होगा। इसे प्राप्त करने के कई तरीके हैं, उनमें से एक कैप्चर समूह के माध्यम से है (कोष्ठक में शामिल रेगुलर एक्सप्रेशन का हिस्सा, यानी (.+)
) जो किसी एक या अधिक वर्ण से मेल खाता हो:
scanner.scan(/\s"(.+)"/) #=> " \"/\""
हालांकि, हम इस scan
. के रिटर्न वैल्यू का उपयोग नहीं करेंगे ऑपरेशन सीधे, लेकिन इसके बजाय पहले कैप्चर समूह का मान प्राप्त करने के लिए कैप्चर का उपयोग करें:
log[:path] = scanner.captures.first #=> "/"
हमने सफलतापूर्वक पथ निकाला और स्कैन पॉइंटर अब समापन दोहरे भाव पर है:
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
लॉग से आईपी पते को पार्स करने के लिए, हम एक बार फिर skip
. का उपयोग करते हैं रिक्त स्थान से घिरे "के लिए" स्ट्रिंग को अनदेखा करने के लिए और फिर scan_until
. का उपयोग करें एक या अधिक गैर-व्हाट्सएप वर्णों से मिलान करने के लिए (\s
व्हाइटस्पेस और [^\s]
. का प्रतिनिधित्व करने वाला वर्ण वर्ग है इसका निषेध है):
scanner.skip(/ for /) #=> 5
log[:ip] = scanner.scan_until(/[^\s]+/) #=> "127.0.0.1"
क्या आप बता सकते हैं कि स्कैन पॉइंटर अब कहाँ होगा? एक पल के लिए इसके बारे में सोचें और फिर अपने उत्तर की तुलना समाधान से करें:
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
टाइमस्टैम्प को पार्स करना अब तक बहुत परिचित होना चाहिए। पहले हम भरोसेमंद पुराने skip
. का उपयोग करते हैं शाब्दिक स्ट्रिंग " at "
. पर अनदेखा करने के लिए और फिर scan_until
. का उपयोग करें वर्तमान लाइन के अंत तक पढ़ने के लिए, जिसे $
. द्वारा दर्शाया गया है रेगुलर एक्सप्रेशन में:
scanner.skip(/ at /) #=> 4
log[:timestamp] = scanner.scan_until(/$/) #=> "2017-08-20 20:53:10 +0900"
जिस जानकारी में हम रुचि रखते हैं, वह अंतिम पंक्ति पर HTTP स्थिति कोड है, इसलिए हम "पूर्ण" शब्द के बाद हमें स्पेस में ले जाने के लिए Skip_until का उपयोग करेंगे।
scanner.skip_until(/Completed /) #=> 296
जैसा कि नाम से पता चलता है कि यह scan_until
. के समान काम करता है लेकिन मिलान की गई स्ट्रिंग को वापस करने के बजाय यह छोड़े गए वर्णों की संख्या देता है। यह स्कैन पॉइंटर को उस HTTP स्थिति कोड के ठीक सामने रखता है जिसमें हम रुचि रखते हैं।
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
^
अब इससे पहले कि हम वास्तविक HTTP प्रतिक्रिया कोड को स्कैन करें, क्या यह अच्छा नहीं होगा यदि हम बता सकें कि क्या HTTP प्रतिक्रिया कोड एक सफलता को दर्शाता है (इस उदाहरण के लिए 2xx श्रेणी में कोई कोड) या विफलता (अन्य सभी श्रेणियां)? इसे प्राप्त करने के लिए, हम वास्तव में स्कैन पॉइंटर को हिलाए बिना, अगले वर्ण को देखने के लिए पीक का उपयोग करेंगे।
log[:success] = scanner.peek(1) == "2" #=> true
अब हम अगले तीन वर्णों को पढ़ने के लिए स्कैन का उपयोग कर सकते हैं, जिन्हें रेगुलर एक्सप्रेशन /\d{3}/
द्वारा दर्शाया गया है। :
log[:response_code] = scanner.scan(/\d{3}/) #=> "200"
एक बार फिर से स्कैन पॉइंटर पहले से मेल खाने वाले रेगुलर एक्सप्रेशन के अंत में सही होगा:
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
^
अंतिम जानकारी जो हम अपनी लॉग प्रविष्टि से निकालना चाहते हैं, वह मिलीसेकंड में निष्पादन समय है, जिसे skip
द्वारा प्राप्त किया जा सकता है स्ट्रिंग पर पिंग करें " OK in "
. में और फिर शाब्दिक स्ट्रिंग "ms"
. तक और इसके साथ सब कुछ पढ़ना ।
scanner.skip(/ OK in /) #=> 7
log[:duration] = scanner.scan_until(/ms/) #=> "79ms"
और उस आखिरी बिट के साथ, हमारे पास वह हैश है जो हम चाहते थे।
{
method: "GET",
path: "/"
ip: "127.0.0.1",
timestamp: "2017-08-20 20:53:10 +0900",
success: true,
response_code: "200",
duration: "79ms",
}
सारांश
रूबी का StringScanner
सरल नियमित अभिव्यक्तियों और एक पूर्ण विकसित लेक्सर के बीच एक अच्छा मध्य मैदान है। जटिल स्कैनिंग और पार्सिंग आवश्यकताओं के लिए यह सबसे अच्छा विकल्प नहीं है। लेकिन इसकी सीधी प्रकृति सभी के लिए बुनियादी नियमित अभिव्यक्ति ज्ञान के साथ इनपुट स्ट्रिंग्स से जानकारी निकालना आसान बनाती है और मैंने अतीत में उत्पादन कोड में सफलतापूर्वक उनका उपयोग किया है। हम आशा करते हैं कि आप इस छिपे हुए रत्न को खोज लेंगे।
पुनश्च:हमें बताएं कि आपको क्या लगता है कि छिपे हुए रत्न क्या हैं जिन्हें हमें आगे उजागर करना चाहिए!