shlex --- تحلیل واژگانی ساده

کد منبع: Lib/shlex.py


کلاس shlex نوشتن تحلیل‌گرهای واژگانی برای سینتکس‌های ساده‌ای که شبیه سینتکس پوسته Unix هستند را آسان می‌کند. این امر اغلب برای نوشتن زبان‌های کوچک (minilanguages) (برای مثال، در پرونده‌های کنترل اجرا برای برنامه‌های پایتون) یا برای تجزیه رشته‌های داخل علامت نقل‌قول مفید خواهد بود.

ماژول shlex توابع زیر را تعریف می‌کند:

shlex.split(s, comments=False, posix=True)

رشته‌ی s را با استفاده از سینتکس مشابه پوسته تقسیم می‌کند. اگر comments برابر False باشد (پیش‌فرض)، تجزیه‌ی کامنت‌ها در رشته‌ی داده‌شده غیرفعال خواهد شد (با تنظیم ویژگی commenters در نمونه‌ی shlex به رشته‌ی خالی). این تابع به‌طور پیش‌فرض در حالت POSIX کار می‌کند، اما اگر آرگومان posix نادرست باشد، از حالت غیر POSIX استفاده می‌کند.

تغییر یافته در نسخه‌ی 3.12: اکنون ارسال None برای آرگومان s به جای خواندن sys.stdin، باعث پرتاب یک استثنا می‌شود.

shlex.join(split_command)

توکن‌های فهرست split_command را به هم می‌چسباند و یک رشته برمی‌گرداند. این تابع معکوس split() است.

>>> from shlex import join
>>> print(join(['echo', '-n', 'Multiple words']))
echo -n 'Multiple words'

مقدار بازگشتی برای محافظت در برابر آسیب‌پذیری‌های تزریق، برای پوسته خنثی شده است (به quote() مراجعه کنید).

اضافه شده در نسخه‌ی 3.8.

shlex.quote(s)

نسخه‌ای خنثی‌شده برای پوسته از رشته‌ی s برمی‌گرداند. مقدار برگردانده‌شده رشته‌ای است که می‌تواند با اطمینان به‌عنوان یک توکن در خط فرمان پوسته استفاده شود؛ برای مواردی که نمی‌توانید از یک فهرست استفاده کنید.

هشدار

ماژول shlex فقط برای پوسته‌های Unix طراحی شده است.

تضمین نمی‌شود که تابع quote() روی پوسته‌های غیرسازگار با POSIX یا پوسته‌هایی از سیستم‌عامل‌های دیگر مانند ویندوز درست عمل کند. اجرای دستورهایی که توسط این ماژول نقل‌قول شده‌اند روی چنین پوسته‌هایی می‌تواند امکان آسیب‌پذیری تزریق دستور را ایجاد کند.

استفاده از توابعی را در نظر بگیرید که آرگومان‌های فرمان را با فهرست‌ها منتقل می‌کنند، مانند subprocess.run() با shell=False.

این روش ناایمن خواهد بود:

>>> filename = 'somefile; rm -rf ~'
>>> command = 'ls -l {}'.format(filename)
>>> print(command)  # executed by a shell: boom!
ls -l somefile; rm -rf ~

quote() به شما امکان می‌دهد حفره‌ی امنیتی را ببندید:

>>> from shlex import quote
>>> command = 'ls -l {}'.format(quote(filename))
>>> print(command)
ls -l 'somefile; rm -rf ~'
>>> remote_command = 'ssh home {}'.format(quote(command))
>>> print(remote_command)
ssh home 'ls -l '"'"'somefile; rm -rf ~'"'"''

نقل‌قول‌گذاری با پوسته‌های UNIX و با split() سازگار است:

>>> from shlex import split
>>> remote_command = split(remote_command)
>>> remote_command
['ssh', 'home', "ls -l 'somefile; rm -rf ~'"]
>>> command = split(remote_command[-1])
>>> command
['ls', '-l', 'somefile; rm -rf ~']

اضافه شده در نسخه‌ی 3.3.

ماژول shlex کلاس زیر را تعریف می‌کند:

class shlex.shlex(instream=None, infile=None, posix=False, punctuation_chars=False)

یک نمونه از shlex یا نمونه‌ای از یک زیرکلاس، یک شیء تحلیل‌گر واژگانی است. آرگومان مقداردهی اولیه، در صورت وجود، مشخص می‌کند که نویسه‌ها از کجا خوانده شوند. این آرگومان باید یک شیء شبه‌پرونده/جریان‌مانند با متدهای read() و readline() یا یک رشته باشد. اگر آرگومانی داده نشود، ورودی از sys.stdin گرفته خواهد شد. دومین آرگومان اختیاری یک رشته‌ی نام پرونده است که مقدار اولیه ویژگی infile را تنظیم می‌کند. اگر آرگومان instream حذف شود یا برابر sys.stdin باشد، این آرگومان دوم به‌طور پیش‌فرض "stdin" خواهد بود. آرگومان posix حالت عملیاتی را تعریف می‌کند: وقتی posix درست نباشد (پیش‌فرض)، نمونه‌ی shlex در حالت سازگاری عمل خواهد کرد. هنگام عمل در حالت POSIX، shlex تلاش می‌کند تا حد ممکن به قوانین تجزیه‌ی پوسته‌ی POSIX نزدیک باشد. آرگومان punctuation_chars راهی فراهم می‌کند تا رفتار حتی به نحوه‌ی تجزیه‌ی پوسته‌های واقعی نزدیک‌تر شود. این آرگومان می‌تواند مقادیر مختلفی بپذیرد: مقدار پیش‌فرض، False، رفتار موجود در Python 3.5 و نسخه‌های قدیمی‌تر را حفظ می‌کند. اگر روی True تنظیم شود، تجزیه‌ی نویسه‌های ();<>|& تغییر می‌کند: هر دنباله‌ی پیوسته‌ای از این نویسه‌ها (که نویسه‌های نمادگذاری محسوب می‌شوند) به‌صورت یک توکن واحد بازگردانده می‌شود. اگر روی یک رشته‌ی غیرخالی از نویسه‌ها تنظیم شود، آن نویسه‌ها به‌عنوان نویسه‌های نمادگذاری استفاده خواهند شد. هر نویسه‌ای در ویژگی wordchars که در punctuation_chars وجود داشته باشد، از wordchars حذف خواهد شد. برای اطلاعات بیشتر، بهبود سازگاری با پوسته‌ها را ببینید. punctuation_chars فقط هنگام ایجاد نمونه‌ی shlex قابل تنظیم است و نمی‌توان آن را بعداً تغییر داد.

تغییر یافته در نسخه‌ی 3.6: پارامتر punctuation_chars افزوده شد.

همچنین ملاحظه نمائید

ماژول configparser

پارسر برای پرونده‌های پیکربندی مشابه پرونده‌های .ini ویندوز.

اشیاء shlex

یک نمونه از shlex متدهای زیر را دارد:

shlex.get_token()

یک توکن برمی‌گرداند. اگر توکن‌ها با استفاده از push_token() در پشته قرار گرفته باشند، یک توکن را از پشته خارج می‌کند. در غیر این صورت، یکی را از جریان ورودی می‌خواند. اگر خواندن بلافاصله با پایان پرونده مواجه شود، eof برگردانده می‌شود (رشته خالی ('') در حالت غیر POSIX و None در حالت POSIX).

shlex.push_token(str)

آرگومان را روی پشته‌ی توکن قرار دهید.

shlex.read_token()

یک توکن خام را بخوانید. پشته‌ی پس‌زدن (pushback stack) را نادیده بگیرید و درخواست‌های منبع را تفسیر نکنید. (این معمولاً نقطه‌ی ورود مفیدی نیست و تنها به‌خاطر کامل بودن در اینجا مستند شده است.)

shlex.sourcehook(filename)

هنگامی که shlex یک درخواست منبع را تشخیص می‌دهد (به source در زیر مراجعه کنید)، این متد توکن بعدی را به‌عنوان آرگومان دریافت می‌کند و انتظار می‌رود یک تاپل شامل یک نام پرونده و یک شیء شبه‌پرونده باز را برگرداند.

به‌طور معمول، این متد ابتدا هرگونه علامت نقل‌قول اطراف آرگومان را حذف می‌کند. اگر نتیجه یک مسیر مطلق باشد، یا هیچ درخواست منبع قبلی‌ای فعال نباشد، یا منبع قبلی یک جریان (مانند sys.stdin) باشد، نتیجه بدون تغییر باقی می‌ماند. در غیر این صورت، اگر نتیجه یک مسیر نسبی باشد، بخش پوشه از نام پرونده‌ای که بلافاصله قبل از آن در پشته‌ی درج منبع قرار دارد، به ابتدای آن افزوده می‌شود (این رفتار مشابه روشی است که پیش‌پردازنده‌ی C با #include "file.h" برخورد می‌کند).

نتیجه‌ی دستکاری‌ها به‌عنوان یک نام پرونده در نظر گرفته می‌شود و به‌عنوان اولین کامپوننت تاپل برگردانده می‌شود، و open() روی آن فراخوانی می‌شود تا کامپوننت دوم را به دست دهد. (توجه: این، معکوس ترتیب آرگومان‌ها در مقداردهی اولیه نمونه است!)

این قلاب در دسترس قرار گرفته است تا بتوانید از آن برای پیاده‌سازی مسیرهای جستجوی پوشه، افزودن پسوندهای پرونده و سایر ترفندهای فضای نام استفاده کنید. هیچ قلاب 'close' متناظری وجود ندارد، اما یک نمونه از shlex، هنگامی که جریان ورودی منبع EOF را برگرداند، متد close() آن را فراخوانی می‌کند.

برای کنترل صریح‌تر پشته‌سازی منبع، از متدهای push_source() و pop_source() استفاده کنید.

shlex.push_source(newstream, newfile=None)

یک جریان منبع ورودی را روی پشته ورودی قرار می‌دهد. اگر آرگومان filename تعیین شده باشد، بعداً برای استفاده در پیام‌های خطا در دسترس خواهد بود. این همان متدی است که به‌صورت داخلی توسط متد sourcehook() استفاده می‌شود.

shlex.pop_source()

آخرین منبع ورودی واردشده به پشته را از پشته ورودی بردارید. این همان متدی است که به‌صورت داخلی هنگامی استفاده می‌شود که تحلیل‌گر واژگانی (lexer) در یک جریان ورودی پشته‌ای به EOF برسد.

shlex.error_leader(infile=None, lineno=None)

این متد یک سرآغاز پیام خطا در قالب برچسب خطای کامپایلر C یونیکس تولید می‌کند؛ قالب به‌صورت '"%s", line %d: ' است، که در آن %s با نام پرونده منبع جاری و %d با شماره خط ورودی جاری جایگزین می‌شود (می‌توان از آرگومان‌های اختیاری برای بازنویسی این مقادیر استفاده کرد).

این سهولت فراهم شده است تا کاربران shlex تشویق شوند پیام‌های خطا را در قالب استاندارد و قابل‌تجزیه‌ای تولید کنند که Emacs و سایر ابزارهای Unix آن را درک می‌کنند.

نمونه‌های زیرکلاس‌های shlex دارای چند متغیر نمونه عمومی هستند که یا تحلیل واژگانی را کنترل می‌کنند یا می‌توان از آن‌ها برای اشکال‌زدایی استفاده کرد:

shlex.commenters

رشته‌ای از نویسه‌ها که به‌عنوان آغازگرهای توضیح شناخته می‌شوند. تمام نویسه‌ها از آغازگر توضیح تا پایان خط نادیده گرفته می‌شوند. به‌طور پیش‌فرض فقط شامل '#' است.

shlex.wordchars

رشته‌ای از نویسه‌ها که برای تشکیل توکن‌های چند نویسه‌ای انباشته می‌شوند. به‌طور پیش‌فرض، شامل تمام نویسه‌های الفباعددی ASCII و زیرخط می‌شود. در حالت POSIX، نویسه‌های لهجه‌دار در مجموعه Latin-1 نیز گنجانده می‌شوند. اگر punctuation_chars خالی نباشد، نویسه‌های ~-./*?=، که می‌توانند در مشخصات نام پرونده و پارامترهای خط فرمان ظاهر شوند، نیز در این ویژگی گنجانده می‌شوند و هر نویسه‌ای که در punctuation_chars ظاهر شود، در صورت وجود در wordchars، از آن حذف می‌شود. اگر whitespace_split روی True تنظیم شده باشد، این مورد بی‌اثر خواهد بود.

shlex.whitespace

تویسه‌هایی که فضای سفید در نظر گرفته می‌شوند و نادیده گرفته خواهند شد. فضای سفید مرز توکن‌ها را مشخص می‌کند. به‌طور پیش‌فرض، شامل فاصله، تب، تغذیه سطر و بازگشت به ابتدای سطر است.

shlex.escape

نویسه‌هایی که به‌عنوان نویسه‌های خنثی‌کننده در نظر گرفته می‌شوند. این مورد فقط در حالت POSIX استفاده می‌شود و به‌طور پیش‌فرض فقط شامل '\' است.

shlex.quotes

نویسه‌هایی که به‌عنوان علامت‌های نقل‌قول رشته در نظر گرفته می‌شوند. توکن تا زمانی که دوباره همان علامت نقل‌قول مشاهده شود انباشته می‌شود (بنابراین، انواع مختلف علامت‌های نقل‌قول، مانند پوسته، از یکدیگر محافظت می‌کنند). به‌طور پیش‌فرض، شامل علامت‌های نقل‌قول تکی و دوتایی ASCII است.

shlex.escapedquotes

نویسه‌های موجود در quotes که نویسه‌های خنثی‌کننده‌ی تعریف‌شده در escape را تفسیر می‌کنند. این مورد فقط در حالت POSIX استفاده می‌شود و به‌طور پیش‌فرض فقط شامل '"' است.

shlex.whitespace_split

اگر True باشد، توکن‌ها فقط بر اساس فضای سفید جدا می‌شوند. این قابلیت مفید است، برای مثال، برای تجزیه سطرهای فرمان با shlex و دریافت توکن‌ها به شیوه‌ای مشابه آرگومان‌های پوسته. هنگامی که همراه با punctuation_chars استفاده شود، توکن‌ها علاوه بر آن نویسه‌ها، بر اساس فضای سفید نیز جدا می‌شوند.

تغییر یافته در نسخه‌ی 3.8: ویژگی punctuation_chars با ویژگی whitespace_split سازگار شد.

shlex.infile

نام پرونده ورودی فعلی، که در ابتدا در زمان نمونه‌سازی کلاس تنظیم شده یا توسط درخواست‌های منبع بعدی روی پشته قرار گرفته است. بررسی این مقدار ممکن است هنگام ساخت پیام‌های خطا مفید باشد.

shlex.instream

جریان ورودی‌ای که این نمونه‌ی shlex نویسه‌ها را از آن می‌خواند.

shlex.source

این ویژگی به‌طور پیش‌فرض None است. اگر یک رشته به آن انتساب دهید، آن رشته به‌عنوان یک درخواست درج در سطح واژگانی مشابه کلیدواژه source در پوسته‌های مختلف تشخیص داده می‌شود. یعنی توکن بلافاصله بعدی به‌عنوان نام پرونده باز می‌شود و ورودی تا EOF از آن جریان دریافت می‌شود؛ در آن نقطه، متد close() آن جریان فراخوانی می‌شود و منبع ورودی دوباره جریان ورودی اصلی خواهد شد. درخواست‌های منبع می‌توانند تا هر تعداد سطحی روی هم انباشته شوند.

shlex.debug

اگر این ویژگی عددی و 1 یا بیشتر باشد، نمونه‌ای از shlex خروجی پیشرفت پرجزئیاتی درباره‌ی رفتار آن چاپ می‌کند. اگر نیاز به استفاده از این دارید، می‌توانید کد منبع ماژول را بخوانید تا جزئیات را بیاموزید.

shlex.lineno

شماره خط منبع (تعداد سطرهای جدید دیده‌شده تاکنون به‌علاوه یک).

shlex.token

بافر توکن (token buffer). ممکن است بررسی این مورد هنگام گرفتن استثناها مفید باشد.

shlex.eof

توکنی که برای تعیین پایان پرونده به کار می‌رود. این مقدار در حالت غیر POSIX به رشته‌ی خالی ('') و در حالت POSIX به None تنظیم می‌شود.

shlex.punctuation_chars

یک ویژگی فقط‌خواندنی. نویسه‌هایی که به‌عنوان نمادگذاری تلقی می‌شوند. دنباله‌های پیوسته‌ای از نویسه‌های نمادگذاری به‌عنوان یک توکن واحد بازگردانده خواهند شد. با این حال، توجه داشته باشید که هیچ‌گونه بررسی صحت معنایی انجام نخواهد شد: برای مثال، ممکن است '>>>' به‌عنوان یک توکن بازگردانده شود، حتی اگر پوسته‌ها آن را به‌عنوان چنین توکنی نشناسند.

اضافه شده در نسخه‌ی 3.6.

قواعد تجزیه

هنگام کار در حالت non-POSIX، shlex سعی می‌کند از قوانین زیر پیروی کند.

  • نویسه‌های نقل‌قول درون کلمات تشخیص داده نمی‌شوند (Do"Not"Separate به‌عنوان کلمه‌ی واحد Do"Not"Separate تجزیه می‌شود)؛

  • نویسه‌های خنثی‌سازی شناسایی نمی‌شوند؛

  • قرار دادن نویسه‌ها درون علامت‌های نقل‌قول، مقدار لفظی تمام نویسه‌های درون علامت‌های نقل‌قول را حفظ می‌کند؛

  • علامت‌های نقل‌قول پایانی، واژه‌ها را جدا می‌کنند ("Do"Separate به‌صورت "Do" و Separate تجزیه می‌شود)؛

  • اگر whitespace_split برابر False باشد، هر نویسه‌ای که به‌عنوان نویسه کلمه‌ای، فضای سفید یا علامت نقل‌قول اعلام نشده باشد، به‌صورت یک توکن تک‌نویسه‌ای برگردانده می‌شود. اگر True باشد، shlex فقط کلمات را بر اساس فضای سفید جدا می‌کند؛

  • EOF با یک رشته خالی ('') اعلام می‌شود؛

  • تجزیه‌ی رشته‌های خالی ممکن نیست، حتی اگر داخل علامت نقل‌قول باشند.

هنگام کار در حالت POSIX، shlex تلاش می‌کند از قواعد تجزیه زیر پیروی کند.

  • علامت‌های نقل‌قول حذف می‌شوند و کلمات را جدا نمی‌کنند ("Do"Not"Separate" به‌عنوان کلمه‌ی واحد DoNotSeparate تجزیه می‌شود)؛

  • نویسه‌های خنثی‌سازی بدون علامت نقل‌قول (مثلاً '\') مقدار لفظی نویسه‌ای که پس از آن‌ها می‌آید را حفظ می‌کنند؛

  • قراردادن نویسه‌ها درون علامت‌های نقل‌قولی که بخشی از escapedquotes نیستند (مانند "'") مقدار لفظی همه نویسه‌های درون علامت‌های نقل‌قول را حفظ می‌کند؛

  • دربرگرفتن نویسه‌ها با علامت‌های نقل‌قولی که بخشی از escapedquotes هستند (مثلاً '"')، مقدار لفظی همه نویسه‌های درون علامت‌های نقل‌قول را حفظ می‌کند، به‌جز نویسه‌های ذکرشده در escape. نویسه‌های خنثی‌سازی معنای ویژه خود را فقط زمانی حفظ می‌کنند که پس از آن‌ها علامت نقل‌قول مورد استفاده یا خود نویسه خنثی‌سازی بیاید. در غیر این صورت، نویسه خنثی‌سازی یک نویسه معمولی در نظر گرفته می‌شود.

  • پایان پرونده با مقدار None اعلام می‌شود؛

  • رشته‌های خالی داخل علامت نقل‌قول ('') مجاز هستند.

بهبود سازگاری با پوسته‌ها

اضافه شده در نسخه‌ی 3.6.

کلاس shlex سازگاری با تجزیه‌ای را فراهم می‌کند که پوسته‌های رایج یونیکس مانند bash، dash و sh انجام می‌دهند. برای بهره‌مندی از این سازگاری، آرگومان punctuation_chars را در سازنده مشخص کنید. مقدار پیش‌فرض آن False است که رفتار پیش از 3.6 را حفظ می‌کند. با این حال، اگر روی True تنظیم شود، تجزیه نویسه‌های ();<>|& تغییر می‌کند: هر دنباله‌ای از این نویسه‌ها به‌عنوان یک توکن واحد بازگردانده می‌شود. هرچند این قابلیت به‌اندازه یک پارسر کامل برای پوسته‌ها نیست (که با توجه به تعدد پوسته‌های موجود، خارج از حیطه کتابخانه استاندارد خواهد بود)، اما به شما امکان می‌دهد پردازش سطرهای فرمان را آسان‌تر از آنچه در غیر این صورت ممکن بود انجام دهید. برای نمایش، می‌توانید تفاوت را در قطعه کد زیر ببینید:

>>> import shlex
>>> text = "a && b; c && d || e; f >'abc'; (def \"ghi\")"
>>> s = shlex.shlex(text, posix=True)
>>> s.whitespace_split = True
>>> list(s)
['a', '&&', 'b;', 'c', '&&', 'd', '||', 'e;', 'f', '>abc;', '(def', 'ghi)']
>>> s = shlex.shlex(text, posix=True, punctuation_chars=True)
>>> s.whitespace_split = True
>>> list(s)
['a', '&&', 'b', ';', 'c', '&&', 'd', '||', 'e', ';', 'f', '>', 'abc', ';',
'(', 'def', 'ghi', ')']

البته، توکن‌هایی برگردانده خواهند شد که برای پوسته‌ها معتبر نیستند، و شما باید بررسی‌های خطای خودتان را روی توکن‌های برگردانده‌شده پیاده‌سازی کنید.

به‌جای ارسال True به‌عنوان مقدار پارامتر punctuation_chars، می‌توانید رشته‌ای با نویسه‌های مشخص را ارسال کنید که برای تعیین اینکه کدام نویسه‌ها نمادگذاری محسوب می‌شوند، استفاده خواهد شد. برای مثال:

>>> import shlex
>>> s = shlex.shlex("a && b || c", punctuation_chars="|")
>>> list(s)
['a', '&', '&', 'b', '||', 'c']

توجه

هنگامی که punctuation_chars مشخص شده باشد، نویسه‌های ~-./*?= به ویژگی wordchars افزوده می‌شوند. دلیل آن این است که این نویسه‌ها می‌توانند در نام پرونده‌ها (از جمله وایلدکارد) و آرگومان‌های خط فرمان ظاهر شوند (برای مثال --color=auto). بنابراین:

>>> import shlex
>>> s = shlex.shlex('~/a && b-c --color=auto || d *.py?',
...                 punctuation_chars=True)
>>> list(s)
['~/a', '&&', 'b-c', '--color=auto', '||', 'd', '*.py?']

با این حال، برای تطبیق هرچه نزدیک‌تر با پوسته، توصیه می‌شود هنگام استفاده از punctuation_chars، همیشه از posix و whitespace_split استفاده شود؛ این کار wordchars را به‌طور کامل بی‌اثر می‌کند.

برای بهترین نتیجه، punctuation_chars باید همراه با posix=True تنظیم شود. (توجه داشته باشید که posix=False برای shlex پیش‌فرض است.)