؟". سواء أكان التمرير القوي أو أدوات grep أو sed المعتادة ، يريد مطورو Windows الوص...">

نحن ندمج أوامر Linux في Windows باستخدام PowerShell و WSL

سؤال مطور نموذجي لـ Windows: "لماذا لا يزال غير موجود هنا < LINUX> ؟". سواء أكان التمرير القوي أو أدوات grep أو sed المعتادة ، يريد مطورو Windows الوصول بسهولة إلى هذه الأوامر في العمل اليومي.

اتخذ نظام Windows الفرعي لنظام Linux (WSL) خطوة كبيرة للأمام في هذا الصدد. يسمح لك بالاتصال بأوامر Linux من Windows ، وتعديلها عبر wsl.exe (على سبيل المثال ، wsl ls ). على الرغم من أن هذا يعد تحسينًا كبيرًا ، إلا أن هذا الخيار يعاني من عدد من العيوب.

  • إضافة في كل مكان من wsl متعب وغير طبيعي.
  • لا تعمل مسارات Windows في الوسيطات دائمًا ، لأنه يتم تفسير خطوط مائلة عكسية كأحرف هروب وليست فواصل للدليل.
  • لا يتم ترجمة مسارات Windows في الوسائط إلى نقطة التحميل المقابلة في WSL.
  • لا يتم مراعاة الإعدادات الافتراضية في ملفات تعريف WSL ذات الأسماء المستعارة ومتغيرات البيئة.
  • إكمال مسار Linux غير مدعوم.
  • إكمال الأوامر غير مدعوم.
  • اكتمال الوسيطة غير مدعوم.

نتيجةً لذلك ، يُنظر إلى أوامر Linux في نظام Windows كمواطنين من الدرجة الثانية - وهي أكثر صعوبة في الاستخدام من الفرق الأصلية. لتحقيق المساواة في حقوقهم ، تحتاج إلى حل هذه المشاكل.

قذائف PowerShell


باستخدام مغلفات وظيفة PowerShell ، يمكننا إضافة إكمال الأمر wsl الحاجة إلى بادئات wsl خلال ترجمة مسارات Windows إلى مسارات WSL. المتطلبات الأساسية للقذائف:

  • يجب أن يحتوي كل أمر Linux على قشرة واحدة من الوظيفة بنفس الاسم.
  • يجب أن يتعرف shell على مسارات Windows التي تم تمريرها كوسائط وتحويلها إلى مسارات WSL.
  • يجب أن يقوم shell باستدعاء wsl باستخدام الأمر Linux المناسب لأي إدخال لخطوط الأنابيب ويمرر أي وسيطات لسطر الأوامر تنتقل إلى الوظيفة.

نظرًا لأنه يمكن تطبيق هذا القالب على أي أمر ، فيمكننا تجريد تعريف هذه الأصداف وإنشاءها ديناميكيًا من قائمة الأوامر للاستيراد.

 # The commands to import. $commands = "awk", "emacs", "grep", "head", "less", "ls", "man", "sed", "seq", "ssh", "tail", "vim" # Register a function for each command. $commands | ForEach-Object { Invoke-Expression @" Remove-Alias $_ -Force -ErrorAction Ignore function global:$_() { for (`$i = 0; `$i -lt `$args.Count; `$i++) { # If a path is absolute with a qualifier (eg C:), run it through wslpath to map it to the appropriate mount point. if (Split-Path `$args[`$i] -IsAbsolute -ErrorAction Ignore) { `$args[`$i] = Format-WslArgument (wsl.exe wslpath (`$args[`$i] -replace "\\", "/")) # If a path is relative, the current working directory will be translated to an appropriate mount point, so just format it. } elseif (Test-Path `$args[`$i] -ErrorAction Ignore) { `$args[`$i] = Format-WslArgument (`$args[`$i] -replace "\\", "/") } } if (`$input.MoveNext()) { `$input.Reset() `$input | wsl.exe $_ (`$args -split ' ') } else { wsl.exe $_ (`$args -split ' ') } } "@ } 

تحدد قائمة $command أوامر الاستيراد. بعد ذلك ، نقوم بإنشاء مجمع دالة بشكل حيوي لكل منهم باستخدام أمر Invoke-Expression (أولاً إزالة أي أسماء مستعارة تتعارض مع الوظيفة).

تقوم الوظيفة بالتكرار على وسيطات سطر الأوامر ، وتحدد مسارات Windows باستخدام أوامر Split-Path و Test-Path ، ثم تحول هذه المسارات إلى مسارات WSL. نقوم بتشغيل المسارات من خلال وظيفة المساعد Format-WslArgument ، والتي Format-WslArgument لاحقًا. إنها تفلت من الأحرف الخاصة ، مثل المسافات والأقواس ، والتي يمكن إساءة تفسيرها بطريقة أخرى.

أخيرًا ، نقوم بتمرير إدخال wsl وأي وسيطات سطر أوامر إلى wsl .

باستخدام هذه الأغلفة ، يمكنك استدعاء أوامر Linux المفضلة لديك بطريقة أكثر طبيعية دون إضافة بادئة wsl ودون القلق بشأن كيفية تحويل المسارات:

  • man bash
  • less -i $profile.CurrentUserAllHosts
  • ls -Al C:\Windows\ | less
  • grep -Ein error *.log
  • tail -f *.log

يتم عرض مجموعة الأوامر الأساسية هنا ، ولكن يمكنك إنشاء shell لأي أمر Linux عن طريق إضافته ببساطة إلى القائمة. إذا قمت بإضافة هذا الرمز إلى ملف التعريف الخاص بك على PowerShell ، فستكون هذه الأوامر متاحة لك في كل جلسة من جلسات PowerShell ، وكذلك الأوامر الأصلية!

الخيارات الافتراضية


في نظام Linux ، من المعتاد تحديد الأسماء المستعارة و / أو متغيرات البيئة في ملفات التعريف (ملف تعريف تسجيل الدخول) ، وتحديد المعلمات الافتراضية للأوامر المستخدمة بشكل متكرر (على سبيل المثال ، alias ls=ls -AFh أو export LESS=-i ). أحد عيوب wsl.exe shell wsl.exe غير التفاعلي هو عدم تحميل ملفات التعريف ، وبالتالي فإن هذه الخيارات غير متوفرة بشكل افتراضي (على سبيل المثال ، ls في WSL و wsl ls سوف تتصرف بشكل مختلف مع الاسم المستعار المحدد أعلاه).

يوفر PowerShell $ PSDefaultParameterValues ، وهي آلية قياسية لتحديد المعلمات الافتراضية ، ولكن فقط لأوامر cmdlets والوظائف المتقدمة. بالطبع ، يمكنك إنشاء وظائف متقدمة من الأصداف الخاصة بنا ، لكن هذا يقدم تعقيدات غير ضرورية (على سبيل المثال ، يطابق PowerShell أسماء المعلمات الجزئية (على سبيل المثال ، -a يتوافق مع -ArgumentList ) ، والتي -ArgumentList مع أوامر Linux التي تقبل الأسماء الجزئية -ArgumentList ) ، و لن يكون بناء الجملة لتحديد القيم الافتراضية هو الأنسب (لتحديد الوسيطات الافتراضية ، مطلوب اسم المعلمة في المفتاح ، وليس فقط اسم الأمر).

ومع ذلك ، مع تعديل بسيط على الأصداف لدينا ، يمكننا تطبيق نموذج مماثل ل $PSDefaultParameterValues وتمكين الخيارات الافتراضية لأوامر Linux!

 function global:$_() { … `$defaultArgs = ((`$WslDefaultParameterValues.$_ -split ' '), "")[`$WslDefaultParameterValues.Disabled -eq `$true] if (`$input.MoveNext()) { `$input.Reset() `$input | wsl.exe $_ `$defaultArgs (`$args -split ' ') } else { wsl.exe $_ `$defaultArgs (`$args -split ' ') } } 

$WslDefaultParameterValues إلى سطر الأوامر ، نرسل المعلمات من خلال wsl.exe . يوضح التالي كيفية إضافة إرشادات إلى ملف تعريف PowerShell لتكوين الإعدادات الافتراضية. الآن يمكننا أن نفعل ذلك!

 $WslDefaultParameterValues["grep"] = "-E" $WslDefaultParameterValues["less"] = "-i" $WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" 

نظرًا $PSDefaultParameterValues المعلمات بعد $PSDefaultParameterValues ، يمكنك بسهولة إيقاف تشغيلها مؤقتًا عن طريق تعيين مفتاح "Disabled" على $true . ميزة إضافية لجدول التجزئة منفصلة هي القدرة على تعطيل $WslDefaultParameterValues بشكل منفصل عن $PSDefaultParameterValues .

استكمال الحجة


يسمح PowerShell بتسجيل وحدات إنهاء الوسيطة باستخدام الأمر Register-ArgumentCompleter . باش لديه أدوات قوية للبرمجة الإنجاز . تسمح لك WSL باستدعاء bash من PowerShell. إذا تمكنا من تسجيل وحدات إنهاء الوسيطة لمغلفات وظيفة PowerShell الخاصة بنا واستدعاء bash لإنشاء الإنهاء ، فسنحصل على إكمال الوسيطات بالكامل بنفس الدقة كما في bash نفسها!

 # Register an ArgumentCompleter that shims bash's programmable completion. Register-ArgumentCompleter -CommandName $commands -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) # Map the command to the appropriate bash completion function. $F = switch ($commandAst.CommandElements[0].Value) { {$_ -in "awk", "grep", "head", "less", "ls", "sed", "seq", "tail"} { "_longopt" break } "man" { "_man" break } "ssh" { "_ssh" break } Default { "_minimal" break } } # Populate bash programmable completion variables. $COMP_LINE = "`"$commandAst`"" $COMP_WORDS = "('$($commandAst.CommandElements.Extent.Text -join "' '")')" -replace "''", "'" for ($i = 1; $i -lt $commandAst.CommandElements.Count; $i++) { $extent = $commandAst.CommandElements[$i].Extent if ($cursorPosition -lt $extent.EndColumnNumber) { # The cursor is in the middle of a word to complete. $previousWord = $commandAst.CommandElements[$i - 1].Extent.Text $COMP_CWORD = $i break } elseif ($cursorPosition -eq $extent.EndColumnNumber) { # The cursor is immediately after the current word. $previousWord = $extent.Text $COMP_CWORD = $i + 1 break } elseif ($cursorPosition -lt $extent.StartColumnNumber) { # The cursor is within whitespace between the previous and current words. $previousWord = $commandAst.CommandElements[$i - 1].Extent.Text $COMP_CWORD = $i break } elseif ($i -eq $commandAst.CommandElements.Count - 1 -and $cursorPosition -gt $extent.EndColumnNumber) { # The cursor is within whitespace at the end of the line. $previousWord = $extent.Text $COMP_CWORD = $i + 1 break } } # Repopulate bash programmable completion variables for scenarios like '/mnt/c/Program Files'/<TAB> where <TAB> should continue completing the quoted path. $currentExtent = $commandAst.CommandElements[$COMP_CWORD].Extent $previousExtent = $commandAst.CommandElements[$COMP_CWORD - 1].Extent if ($currentExtent.Text -like "/*" -and $currentExtent.StartColumnNumber -eq $previousExtent.EndColumnNumber) { $COMP_LINE = $COMP_LINE -replace "$($previousExtent.Text)$($currentExtent.Text)", $wordToComplete $COMP_WORDS = $COMP_WORDS -replace "$($previousExtent.Text) '$($currentExtent.Text)'", $wordToComplete $previousWord = $commandAst.CommandElements[$COMP_CWORD - 2].Extent.Text $COMP_CWORD -= 1 } # Build the command to pass to WSL. $command = $commandAst.CommandElements[0].Value $bashCompletion = ". /usr/share/bash-completion/bash_completion 2> /dev/null" $commandCompletion = ". /usr/share/bash-completion/completions/$command 2> /dev/null" $COMPINPUT = "COMP_LINE=$COMP_LINE; COMP_WORDS=$COMP_WORDS; COMP_CWORD=$COMP_CWORD; COMP_POINT=$cursorPosition" $COMPGEN = "bind `"set completion-ignore-case on`" 2> /dev/null; $F `"$command`" `"$wordToComplete`" `"$previousWord`" 2> /dev/null" $COMPREPLY = "IFS=`$'\n'; echo `"`${COMPREPLY[*]}`"" $commandLine = "$bashCompletion; $commandCompletion; $COMPINPUT; $COMPGEN; $COMPREPLY" -split ' ' # Invoke bash completion and return CompletionResults. $previousCompletionText = "" (wsl.exe $commandLine) -split '\n' | Sort-Object -Unique -CaseSensitive | ForEach-Object { if ($wordToComplete -match "(.*=).*") { $completionText = Format-WslArgument ($Matches[1] + $_) $true $listItemText = $_ } else { $completionText = Format-WslArgument $_ $true $listItemText = $completionText } if ($completionText -eq $previousCompletionText) { # Differentiate completions that differ only by case otherwise PowerShell will view them as duplicate. $listItemText += ' ' } $previousCompletionText = $completionText [System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterName', $completionText) } } # Helper function to escape characters in arguments passed to WSL that would otherwise be misinterpreted. function global:Format-WslArgument([string]$arg, [bool]$interactive) { if ($interactive -and $arg.Contains(" ")) { return "'$arg'" } else { return ($arg -replace " ", "\ ") -replace "([()|])", ('\$1', '`$1')[$interactive] } } 

الكود ضيق بعض الشيء دون فهم بعض الأجزاء الداخلية للباش ، لكننا نقوم بشكل أساسي بما يلي:

  • نسجِّل " -CommandName Register-ArgumentCompleter لكل مغلفات الدوال لدينا من خلال تمرير قائمة $commands إلى المعلمة -CommandName الخاصة بـ Register-ArgumentCompleter .
  • نقوم بتعيين كل أمر إلى وظيفة shell التي يستخدمها bash للإكمال التلقائي (يستخدم bash $F لتحديد مواصفات الإكمال التلقائي ، اختصار لـ complete -F <FUNCTION> ).
  • تحويل وسيطات PowerShell $wordToComplete و $commandAst و $cursorPosition إلى التنسيق المتوقع بواسطة دالات إكمال bash وفقًا لمواصفات إكمال bash القابلة للبرمجة .
  • نقوم بإنشاء سطر الأوامر للنقل إلى wsl.exe ، والذي يضمن إعداد البيئة الصحيح ، ويقوم باستدعاء وظيفة الإكمال التلقائي المناسبة ويعرض النتائج مع فواصل الأسطر.
  • ثم ندعو wsl بسطر الأوامر ، ونفصل المخرجات بفواصل الأسطر وننشئ CompletionResults ، ونفرزها ونهرب من الأحرف مثل المساحات والأقواس التي قد يتم إساءة تفسيرها.

ونتيجة لذلك ، ستستخدم قذائف أوامر Linux نفس الإكمال التلقائي تمامًا كما في bash! على سبيل المثال:

  • ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>

توفر كل عملية إكمال تلقائي قيمًا خاصة بالوسيطة السابقة ، وقراءة بيانات التكوين ، مثل المضيفين المعروفين ، من WSL!

سوف <TAB> التنقل بين المعلمات. سيعرض <Ctrl + > جميع الخيارات المتاحة.

أيضًا ، نظرًا لأن bash autocomplete يعمل معنا الآن ، يمكنك الإكمال التلقائي لمسارات Linux مباشرة في PowerShell!

  • less /etc/<TAB>
  • ls /usr/share/<TAB>
  • vim ~/.bash<TAB>

في الحالات التي يفشل فيها الإكمال التلقائي للباش ، يعود PowerShell إلى النظام الافتراضي باستخدام مسارات Windows. وبالتالي ، في الممارسة العملية ، يمكنك في وقت واحد استخدام تلك الطرق وغيرها وفقًا لتقديرك.

استنتاج


باستخدام PowerShell و WSL ، يمكننا دمج أوامر Linux في Windows كتطبيقات أصلية. ليست هناك حاجة للبحث عن بنيات Win32 أو أدوات مساعدة Linux أو مقاطعة سير العمل عن طريق التبديل إلى shell Linux. ما عليك سوى تثبيت WSL وتهيئة ملف التعريف الخاص بك على PowerShell وسرد الأوامر التي تريد استيرادها ! الإكمال التلقائي الغني لمعلمات الأمر والمسار لملفات Linux و Windows هي وظيفة لم يتم العثور عليها حتى في أوامر Windows الأصلية اليوم.

تتوفر شفرة المصدر الكاملة الموضحة أعلاه ، بالإضافة إلى توصيات إضافية لإدراجها في سير العمل هنا .

ما هي أوامر Linux التي تجدها مفيدة للغاية؟ ما الأشياء المألوفة الأخرى المفقودة عند العمل على Windows؟ اكتب التعليقات أو على جيثب !

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


All Articles