حلقات التكرار في PowerShell: الأنواع وأفضل الممارسات

عندما تبدأ في كتابة نصوص PowerShell للمرة الأولى، ستصل في نهاية المطاف إلى مكان حيث ستحتاج إلى معالجة عدة عناصر من مجموعة. في هذا الوقت، ستحتاج إلى العمل على حلقات foreach في PowerShell وتعلم ماهيتها.

تقريبًا جميع لغات البرمجة لديها هيكل يسمى الحلقات؛ وPowerShell ليست استثناءً. واحدة من أنواع الحلقات الأكثر شيوعًا في PowerShell هي حلقة foreach. في أبسط حالاتها، تقرأ حلقة foreach مجموعة كاملة من العناصر وتقوم بتشغيل بعض نوع من الكود لكل عنصر.

واحدة من أكثر الجوانب الخلاطة لحلقة foreach في PowerShell للمبتدئين هي جميع الخيارات المتاحة. ليس هناك طريقة واحدة فقط لمعالجة كل عنصر في المجموعة؛ بل هناك ثلاثة!

في هذه المقالة، ستتعلم كيف تعمل كل نوع من حلقة foreach ومتى يجب استخدام أحدها على الآخر. بحلول نهاية هذه المقالة، ستكون لديك فهم جيد لكل نوع من حلقات foreach.

أساسيات حلقة ForEach في PowerShell

واحدة من أنواع الحلقات الأكثر شيوعًا التي ستستخدمها في PowerShell هي نوع حلقة foreach. تقوم حلقة foreach بقراءة مجموعة من الكائنات (تكرار) وتكتمل عندما تنتهي من الكائن الأخير. المجموعة من الكائنات التي يتم قراءتها عادة ما تكون ممثلة بمصفوفة أو جدول مفاتيح.

ملاحظة: مصطلح التكرار هو مصطلح برمجي يشير إلى كل تشغيلة لحلقة. في كل مرة يكتمل فيها الدورة لحلقة، يُعرف هذا بالتكرار. ويُشار عادة إلى تشغيل حلقة على مجموعة من الكائنات بأنه “التكرار على المجموعة”.

ربما تحتاج إلى إنشاء ملف نصي عبر بعض المجلدات المنتشرة عبر نظام الملفات. لنفترض أن مسارات المجلدات هي C:\Folder، C:\Program Files\Folder2 و C:\Folder3. بدون حلقة، سيكون علينا الإشارة إلى الأمر Add-Content ثلاث مرات.

Add-Content -Path 'C:\Folder\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Folder2\textfile.txt' -Value 'This is the content of the file'

ما هو الفرق الوحيد بين كل من هذه الإشارات إلى الأمر؟ إنه قيمة Path. القيمة المرتبطة بـ Path هي القيمة الوحيدة التي تتغير بين كل من هذه الحالات.

أنت تكرر الكثير من الشفرة. أنت تضيع الوقت بالكتابة وتفتح نفسك للمشاكل فيما بعد. بدلاً من ذلك، يجب عليك إنشاء “مجموعة” تشمل جميع العناصر التي يتغير معها. لهذا المثال، سنستخدم مصفوفة.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

الآن لديك كل هذه المسارات مخزنة في “مجموعة” أو مصفوفة واحدة. أنت الآن جاهز لاستخدام حلقة للتكرار عبر كل من هذه العناصر. قبل القيام بذلك، من الجيد أن نذكر موضوعًا يعترض غالبًا على الجدد في PowerShell. على عكس أنواع الحلقات الأخرى، حلقات الـ foreach ليست جميعها متشابهة.

تقنياً، هناك ثلاثة أنواع من حلقات الـ foreach في PowerShell. على الرغم من أن كل نوع مشابه في الاستخدام، فمن المهم فهم الفرق بينها.

البيان foreach

النوع الأول من حلقات الـ foreach هو بيان. foreach هو كلمة مفتاحية داخلية في PowerShell ليست cmdlet ولا دالة. يُستخدم بيان الـ foreach دائمًا بهذا الشكل: foreach ($i in $array).

$i المتغير يُمثل المكرر أو قيمة كل عنصر في $path أثناء تكراره على كل عنصر في المصفوفة.

يرجى ملاحظة أن متغير المكرر لا يجب أن يكون $i. يمكن أن يكون اسم المتغير أي شيء آخر.

في المثال أدناه، يمكنك تحقيق نفس المهمة كتكرار لمرجع Add-Content بفعل ذلك:

# أنشئ مصفوفة من المجلدات
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

# قم بتنفيذ التكرار لإنشاء نفس الملف في كل مجلد
foreach ($i in $folders) {
    Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"
}

الـ foreach بيان معروف بأنه بديل أسرع من استخدام ForEach-Object الأمر.

أمر ForEach-Object

إذا كان foreach بيان ويمكن استخدامه بطريقة واحدة فقط، فإن ForEach-Object هو أمر يحمل معه معلمات يمكن استخدامها بطرق متعددة. مثل foreach بيان، يمكن لأمر ForEach-Object التكرار على مجموعة من الكائنات. لكن هذه المرة، يمرر مجموعة تلك الكائنات والإجراء الذي يجب اتخاذه على كل كائن كمعلمة كما هو موضح أدناه.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders | ForEach-Object (Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file")

ملحوظة: لتعقيد الأمور، يحمل أمر ForEach-Object اسمًا بديلاً يسمى foreach. اعتمادًا على كيفية استدعاء مصطلح “foreach”، يتم تشغيل الـ foreach بيان أو يتم تشغيل ForEach-Object.

A good way to differentiate these situations is to notice if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

الطريقة foreach()

واحدة من أحدث حلقات الـ foreach تم تقديمها في PowerShell v4 وتسمى طريقة foreach(). توجد هذه الطريقة على كائن مصفوفة أو مجموعة. تحتوي طريقة foreach() على معلمة كتلة نصية قياسية تحتوي على الإجراءات التي يجب أخذها خلال كل تكرار، تمامًا كما في الحالات الأخرى.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders.ForEach({
	Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file"
})

أكبر فارق مع طريقة foreach() هو كيفية عملها تحت الغطاء.

استخدام طريقة foreach() هو أسرع بشكل كبير ويظهر ذلك بوضوح عند التعامل مع مجموعات كبيرة. من المفضل استخدام هذه الطريقة على الطرق الأخرى إذا كان ذلك ممكنًا.

مشروع صغير: التكرار عبر مجموعة من أسماء الخوادم

أحد أكثر الاستخدامات شيوعًا للحلقة في PowerShell هو قراءة مجموعة من الخوادم من مصدر ما والقيام ببعض الإجراءات على كل منها. لمشروعنا الصغير الأول، دعونا نبني بعض الشيفرة التي تسمح لنا بقراءة أسماء الخوادم (واحدة في كل سطر) من ملف نصي وعمل عملية ping على كل خادم لتحديد ما إذا كانوا متصلين أم لا.

List of server names in a text file

بالنسبة لهذه السيناريو، أي نوع من الحلقات تعتقد أنه سيعمل بشكل أفضل؟

لاحظ أنني ذكرت كلمة “مجموعة” كما في “مجموعة من الخوادم”. هذا مؤشر هنا. لديك بالفعل عدد محدد من أسماء الخوادم، وترغب في القيام ببعض الإجراءات على كل منها. يبدو أن هذه هي فرصة رائعة لتجربة الحلقة الأكثر شيوعًا المستخدمة في PowerShell؛ حلقة foreach.

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

أولاً، قم بإنشاء مصفوفة تحتوي على جميع أسماء الخوادم وسمِّها $servers.

$servers = Get-Content .\servers.txt

الآن بعد أن قمت بإنشاء المصفوفة، ستحتاج الآن إلى تأكيد ما إذا كانت كل خادم متصل على الإنترنت أم لا. الأمر الرائع لاختبار اتصال الخادم هو الأمر المسمى Test-Connection. يقوم هذا الأمر بإجراء بعض اختبارات الاتصال على جهاز الكمبيوتر لمعرفة ما إذا كان متصلاً أم لا.

عن طريق تنفيذ Test-Connection داخل حلقة foreach لقراءة كل سطر من الملف، يمكنك تمرير كل اسم خادم يُمثل بالمتغير $server إلى Test-Connection. هكذا يمكنك استخدام PowerShell لتكرار ملف نصي. يمكنك رؤية مثال على كيفية عمل ذلك أدناه.

foreach ($server in $servers) {
	try {
		$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOP
		Write-Output "$server - OK"
	}
	catch {
		Write-Output "$server - $($_.Exception.Message)"
	}
}

عند تنفيذ حلقة foreach، ستبدو الأمور على النحو التالي:

Looping Test-Connection through a list of server names

لقد اختبرت بنجاح اتصال ملف نصي مليء بأسماء الخوادم! في هذه المرحلة، يمكنك الآن إضافة أو إزالة أسماء الخوادم داخل ملف النص بمرونة دون تغيير في الشيفرة.

لقد قمت بتطبيق بنجاح ما كنا ندعو إليه، الطريقة DRY.

أمثلة PowerShell لحلقة ForEach

لنلقي نظرة على بعض الأمثلة حول كيفية استخدام حلقة ForEach. تستند هذه إلى حالات استخدام في العالم الحقيقي تظهر الفكرة التي يمكنك تعديلها لتناسب متطلباتك.

المثال 1: إنشاء ملف في كل مجلد فرعي في دليل باستخدام بيان ForEach

يوضح هذا المثال الاستخدام الشائع لحلقة التكرار في باورشيل على المجلد في الدليل.

لنفترض أن هناك عشرة مجلدات فرعية داخل مجلد C:\ARCHIVE_VOLUMES. يمثل كل مجلد فرعي حجم أرشيف يتم نسخه يوميًا. بعد اكتمال كل نسخة احتياطية ، يتم إنشاء ملف يحمل اسم BackupState.txt داخل كل مجلد يحتوي على التاريخ الذي تم فيه النسخة الاحتياطية.

يمكنك أن ترى مثالًا على كيفية ذلك أدناه.

Sub-directories under C:\ARCHIVE_VOLUMES

يقوم النص أدناه بأربعة إجراءات:

  • يحصل على قائمة جميع المجلدات الفرعية داخل C:\ARCHIVE_VOLUMES
  • يكرر كل مجلد
  • ينشئ ملف نصي باسم BackupState.txt يحتوي على التاريخ والوقت الحالي كقيمة

يستخدم المثال أدناه بيان foreach.

# حدد المجلد على مستوى الأعلى
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# احصل على جميع المجلدات الفرعية بشكل متكرر
$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# أنشئ ملف نصي في كل مجلد فرعي وأضف التاريخ / الوقت الحالي كقيمة.
foreach ($foldername in $Child_Folders.FullName) {
   (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force
}

باستخدام cmdlet Get-ChildItem ، يمكنك تأكيد أن الملفات تم إنشاؤها أو تحديثها داخل كل من المجلدات الفرعية.

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

الصورة أدناه تُظهر نتيجة البرنامج عند عرض جميع ملفات BackupState.txt الموجودة في كل مجلد فرعي.

A text file is created in each sub-directory

مثال ٢: قراءة محتويات كل ملف نصي في المجلدات الفرعية

بعد ذلك، لتوضيح استخدام حلقة الـ PowerShell لكل ملف في دليل، سيقرأ البرنامج أدناه كل ملف BackupState.txt الذي تم إنشاؤه في المثال ١.

  • البحث بشكل متكرر عن جميع ملفات BackupState.txt داخل كل مجلد فرعي.
  • باستخدام تعليمة foreach، قراءة كل ملف نصي للحصول على قيمة “وقت النسخ الاحتياطي الأخير”.
  • عرض النتيجة على الشاشة.
## العثور على جميع ملفات BackupState.txt في C:\ARCHIVE_VOLUMES
$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## قراءة محتويات كل ملف
foreach ($file in $files) {
    Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))
}

بمجرد تنفيذ البرنامج في جلسة PowerShell الخاصة بك، يجب أن ترى نتيجة مماثلة للصورة أدناه. يظهر هذا أن حلقة PowerShell تمر عبر الملفات، وتقرأ المحتويات وتعرض النتيجة.

Using foreach to loop through files and read its contents.

مثال ٣: الحصول على الخدمات وبدء تشغيلها باستخدام ForEach-Object CmdLet

غالبًا ما يحتاج مسؤولو النظام إلى الحصول على حالة الخدمات وتشغيل سير عمل يدوي أو تلقائي لإصلاح أي خدمات فشلت. دعنا نلقي نظرة على البرنامج النموذجي باستخدام تعليمة ForEach-Object.

لهذا المثال، سيقوم البرنامج أدناه بما يلي:

  • الحصول على قائمة الخدمات التي تم تكوينها للبدء التلقائي ولكنها غير مشغلة حاليًا.
  • ثم، يتم تمرير العناصر في القائمة إلى cmdlet ForEach-Object لمحاولة بدء كل خدمة.
  • A message of either success or failed is displayed depending on the result of the Start-Service command.
## احصل على قائمة من الخدمات التلقائية التي تم إيقافها.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## قم بتمرير كل كائن خدمة إلى الأنبوب ومعالجتها باستخدام cmdlet Foreach-Object
$services | ForEach-Object {
    try {
        Write-Host "Attempting to start '$($.DisplayName)'"
        Start-Service -Name $.Name -ErrorAction STOP
        Write-Host "SUCCESS: '$($.DisplayName)' has been started"
    } catch {
        Write-output "FAILED: $($.exception.message)"
    }
}

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

Using the ForEach-Object loop to start services

المثال ٤: قراءة البيانات من CSV باستخدام طريقة ForEach()

استخدام البيانات من ملفات CSV شائع بين مسؤولي النظام. توضع السجلات داخل ملف CSV لجعل تشغيل العمليات الجماعية سهل باستخدام الجمع بين Import-CSV و ForEach. يستخدم هذا الجمع عادةً لـ إنشاء مستخدمين متعددين في Active Directory.

في المثال التالي، يفترض أن لديك ملف CSV يحتوي على عمودين – الاسم الأول و الاسم الأخير. يجب ثم ملء هذا الملف CSV بأسماء المستخدمين الجدد الذين يجب إنشاؤهم. يبدو ملف CSV مثل ما يلي.

"Firstname","Lastname"
"Grimm","Reaper"
"Hell","Boy"
"Rick","Rude"

الآن للكتلة البرمجية التالية. أولاً، استورد ملف CSV عن طريق تمرير مسار المحتوى إلى cmdlet Import-CSV. ثم، باستخدام الطريقة foreach()، قم بتكرار الأسماء وإنشاء مستخدمين جدد في Active Directory.

# استيراد قائمة الأسماء الأولية والأخيرة من ملف CSV
$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# معالجة القائمة
$newUsers.foreach(
    {
        # إنشاء كلمة مرور عشوائية
        $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)
        $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

        # صياغة اسم المستخدم
        $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

        # بناء سمات المستخدم الجديد
        $NewUserParameters = @{
            GivenName       = $_.FirstName
            Surname         = $_.LastName
            Name            = $userName
            AccountPassword = $secPw
        }

        try {
            New-AdUser @NewUserParameters -ErrorAction Stop
            Write-Output "User '$($userName)' has been created."
        }
        catch {
            Write-Output $_.Exception.Message
        }
    }
)

عند التشغيل، يجب أن يكون لديك الآن مستخدم AD مُنشأ لكل صف في ملف CSV!

ملخص

المنطق وراء حلقة foreach في PowerShell ليس مختلفًا عن ذلك في لغات البرمجة الأخرى. يتغير فقط باستخدامه واختيار نسخة من حلقة foreach لمهمة معينة.

في هذه المقالة، تعلمت أنواع الحلقات foreach المختلفة المتوفرة في PowerShell، وما يجب مراعاته لاختيار النوع المناسب للاستخدام. كما رأيت الأنواع الثلاثة من حلقات foreach في العمل باستخدام سيناريوهات عينية مختلفة.

قراءة إضافية

Source:
https://adamtheautomator.com/powershell-foreach/