PowerShell 당 Python 스크립트를 호출하고 PSObject를 전달하고 구문 분석 된 데이터를 반환합니다.

Nov 28 2020

몇 가지 배경 : 현재 내가 가진 MS SQL 서버에서 (50 열이) 4Mio 행을 조회하고 dbatools PowerShell을 (정규식 물건을 많이)로 데이터를 처리 (배치에서 10.000 행 각 쿼리)를 PSObject에와에 다시 작성 SimplySql과 MariaDb . 평균적으로 나는 약을 얻는다. 150 행 / 초 이 성능을 위해 많은 트릭 (Net의 Stringbuilder 등)을 사용해야했습니다.

새로운 요구 사항으로 일부 텍스트 셀의 언어를 감지하고 개인 데이터 (이름 및 주소)를 제거해야합니다. 그 목적을 위해 좋은 파이썬 라이브러리 ( spacy 및 pycld2 )를 찾았습니다 . pycld2로 테스트를했습니다-꽤 좋은 탐지입니다.

설명을위한 단순화 된 코드 (힌트 : 저는 파이썬 멍청이입니다) :

#get data from MS SQL
$data = Invoke-DbaQuery -SqlInstance $Connection -Query $Query -As PSObject -QueryTimeout 1800 for ($i=0;$i -lt $data.length;$i++){ #do a lot of other stuff here #... #finally make lang detection if ($LangDetect.IsPresent){
    $strLang = $tCaseDescription -replace "([^\p{L}\p{N}_\.\s]|`t|`n|`r)+",""
    $arg = "import pycld2 as cld2; isReliable, textBytesFound, details = cld2.detect('" + $strLang + "', isPlainText = True, bestEffort = True);print(details[0][1])"
    $tCaseLang = & $Env:Programfiles\Python39\python.exe -c $arg } else { $tCaseLang = ''
  }
}
#write to MariaDB
Invoke-SqlUpdate -ConnectionName $ConnectionName -Query $Query

이 파이썬 호출은 매번 작동하지만 루프 호출과 매번 pycld2 lib 가져 오기로 인해 성능 (12rows / sec)이 손상됩니다. 따라서 이것은 절름발이 솔루션입니다 :) 또한 위에서 언급했듯이-나는 spacy를 사용하고 싶습니다-개인 데이터를 제거하기 위해 더 많은 열을 구문 분석해야합니다.

전체 PS Parser를 python으로 변환 할 기분이 있는지 확실하지 않습니다.

더 나은 해결책은 PowerShell에서 python으로 전체 PSObject를 전달하고 (PS 루프가 시작되기 전) PSObject와 마찬가지로 Python에서 처리 된 후 반환하는 것입니다.하지만 어떻게 할 수 있는지 모르겠습니다. 파이썬 / 파이썬 함수로 이것을 실현하십시오.

귀하의 접근 방식 / 제안, 기타 아이디어는 무엇입니까? 감사 :)

답변

2 mklement0 Nov 28 2020 at 22:22

다음 간단한 예제는 PowerShell에서 Python 스크립트 ( 이 경우 를 통해 문자열로 전달됨)로 여러 [pscustomobject]( [psobject]) 인스턴스를 전달하는 방법을 보여줍니다 -c.

  • 직렬화 형식으로 JSON사용하여ConvertTo-Json ...

  • ... 그 JSON 전달 비아 파이프 , 파이썬 통해 읽을 수 stdin에 (표준 입력).

중요 :

  • 문자 인코딩 :

    • PowerShell은 $OutputEncoding데이터를 외부 프로그램 (예 : Python)으로 보낼 때 기본 설정 변수에 지정된 인코딩을 사용합니다.이 인코딩 은 PowerShell [Core] v6 + 에서는 기본적으로 BOM이없는 UTF-8 이지만 Windows PowerShell 에서는 아쉽게도 ASCII (!)입니다 .

    • PowerShell이 텍스트 를 외부 프로그램 으로 전송하는 것을 제한 하는 것처럼, 수신 한 내용을 항상 텍스트로 해석 합니다. 즉,에 저장된 인코딩을 기반으로합니다 [Console]::OutputEncoding. 안타깝게도이 문서를 작성하는 현재 PowerShell 버전은 모두 시스템의 OEM 코드 페이지로 기본 설정됩니다 .

    • 두 PowerShell 버전 모두에서 (BOM없는) UTF-8을 보내고 받으려면 (일시적으로) 다음 $OutputEncoding[Console]::OutputEncoding같이 설정 합니다.
      $OutputEncoding = [Console]::OutputEncoding = [System.Text.Utf8Encoding]::new($false)

  • Python 스크립트 가 개체 를 출력 하도록하려면 다시 JSON 사용을 고려합니다.이 JSON 은 PowerShell에서 ConvertFrom-Json.

# Sample input objects.
$data = [pscustomobject] @{ one = 1; two = 2 }, [pscustomobject] @{ one = 10; two = 20 } # Convert to JSON and pipe to Python. ConvertTo-Json $data | python -c @'

import sys, json

# Parse the JSON passed via stdin into a list of dictionaries.
dicts = json.load(sys.stdin)

# Sample processing: print the 'one' entry of each dict.
for dict in dicts:
  print(dict['one'])

'@

전달할 데이터가 한 줄 문자열 모음 인 경우 JSON이 필요하지 않습니다.

$data = 'foo', 'bar', 'baz' $data | python -c @'

import sys

# Sample processing: print each stdin input line enclosed in [...]
for line in sys.stdin:
  print('[' + line.rstrip('\r\n') + ']')

'@

TefoD Nov 30 2020 at 03:48

@ mklement0의 답변에 따라 올바른 문자 인코딩을 고려하여 JSON을 Python에서 Powershell로 반환하여 완성되고 테스트 된 솔루션을 공유하고 싶습니다. 나는 이미 100k 행을 하나의 배치로 시도했습니다-문제가 없으며 완벽하고 초고속 실행 :)

#get data from MS SQL
$query = -join@( 'SELECT `Id`, `CaseSubject`, `CaseDescription`, `AccountCountry`, `CaseLang` ' 'FROM `db`.`table_global` ' 'ORDER BY `Id` DESC, `Id` ASC ' 'LIMIT 10000;' ) $data = Invoke-DbaQuery -SqlInstance $Connection -Query $Query -As PSObject -QueryTimeout 1800

$arg = @' import pycld2 as cld2 import simplejson as json import sys, re, logging def main(): #toggle the logging level to stderr # https://stackoverflow.com/a/6579522/14226613 -> https://docs.python.org/3/library/logging.html#logging.debug logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logging.info('->Encoding Python: ' + str(sys.stdin.encoding)) # consideration of correct character encoding -> https://stackoverflow.com/a/30107752/14226613 # Parse the JSON passed via stdin into a list of dictionaries -> https://stackoverflow.com/a/65051178/14226613 cases = json.load(sys.stdin, 'utf-8') # Sample processing: print the 'one' entry of each dict. # https://regex101.com/r/bymIQS/1 regex = re.compile(r'(?=[^\w\s]).|[\r\n]|\'|\"|\\') # hash table with Country vs Language for 'boosting' the language detection, if pycld2 is not sure lang_country = {'Albania' : 'ALBANIAN', 'Algeria' : 'ARABIC', 'Argentina' : 'SPANISH', 'Armenia' : 'ARMENIAN', 'Austria' : 'GERMAN', 'Azerbaijan' : 'AZERBAIJANI', 'Bangladesh' : 'BENGALI', 'Belgium' : 'DUTCH', 'Benin' : 'FRENCH', 'Bolivia, Plurinational State of' : 'SPANISH', 'Bosnia and Herzegovina' : 'BOSNIAN', 'Brazil' : 'PORTUGUESE', 'Bulgaria' : 'BULGARIAN', 'Chile' : 'SPANISH', 'China' : 'Chinese', 'Colombia' : 'SPANISH', 'Costa Rica' : 'SPANISH', 'Croatia' : 'CROATIAN', 'Czech Republic' : 'CZECH', 'Denmark' : 'DANISH', 'Ecuador' : 'SPANISH', 'Egypt' : 'ARABIC', 'El Salvador' : 'SPANISH', 'Finland' : 'FINNISH', 'France' : 'FRENCH', 'Germany' : 'GERMAN', 'Greece' : 'GREEK', 'Greenland' : 'GREENLANDIC', 'Hungary' : 'HUNGARIAN', 'Iceland' : 'ICELANDIC', 'India' : 'HINDI', 'Iran' : 'PERSIAN', 'Iraq' : 'ARABIC', 'Ireland' : 'ENGLISH', 'Israel' : 'HEBREW', 'Italy' : 'ITALIAN', 'Japan' : 'Japanese', 'Kosovo' : 'ALBANIAN', 'Kuwait' : 'ARABIC', 'Mexico' : 'SPANISH', 'Monaco' : 'FRENCH', 'Morocco' : 'ARABIC', 'Netherlands' : 'DUTCH', 'New Zealand' : 'ENGLISH', 'Norway' : 'NORWEGIAN', 'Panama' : 'SPANISH', 'Paraguay' : 'SPANISH', 'Peru' : 'SPANISH', 'Poland' : 'POLISH', 'Portugal' : 'PORTUGUESE', 'Qatar' : 'ARABIC', 'Romania' : 'ROMANIAN', 'Russia' : 'RUSSIAN', 'San Marino' : 'ITALIAN', 'Saudi Arabia' : 'ARABIC', 'Serbia' : 'SERBIAN', 'Slovakia' : 'SLOVAK', 'Slovenia' : 'SLOVENIAN', 'South Africa' : 'AFRIKAANS', 'South Korea' : 'Korean', 'Spain' : 'SPANISH', 'Sweden' : 'SWEDISH', 'Switzerland' : 'GERMAN', 'Thailand' : 'THAI', 'Tunisia' : 'ARABIC', 'Turkey' : 'TURKISH', 'Ukraine' : 'UKRAINIAN', 'United Arab Emirates' : 'ARABIC', 'United Kingdom' : 'ENGLISH', 'United States' : 'ENGLISH', 'Uruguay' : 'SPANISH', 'Uzbekistan' : 'UZBEK', 'Venezuela' : 'SPANISH'} for case in cases: #concatenate two fiels and clean them a bitfield, so that we not get any faults due line brakes etc. tCaseDescription = regex.sub('', (case['CaseSubject'] + ' ' + case['CaseDescription'])) tCaseAccCountry = case['AccountCountry'] if tCaseAccCountry in lang_country: language = lang_country[tCaseAccCountry] isReliable, textBytesFound, details = cld2.detect(tCaseDescription, isPlainText = True, bestEffort = True, hintLanguage = language) else: isReliable, textBytesFound, details = cld2.detect(tCaseDescription, isPlainText = True, bestEffort = True) #Take Value case['CaseLang'] = details[0][0] #logging.info('->Python processing CaseID: ' + str(case['Id']) + ' / Detected Language: ' + str(case['CaseLang'])) #encode to JSON retVal = json.dumps(cases, 'utf-8') return retVal if __name__ == '__main__': retVal = main() sys.stdout.write(str(retVal)) '@ $dataJson = ConvertTo-Json $data $data = ($dataJson | python -X utf8 -c $arg) | ConvertFrom-Json

foreach($case in $data) {
    $tCaseSubject = $case.CaseSubject -replace "\\", "\\" -replace "'", "\'"
    $tCaseDescription = $case.CaseDescription -replace "\\", "\\" -replace "'", "\'"
    $tCaseLang = $case.CaseLang.substring(0,1).toupper() + $case.CaseLang.substring(1).tolower() $tCaseId = $case.Id $qUpdate = -join @(
        "UPDATE db.table_global SET CaseSubject=`'$tCaseSubject`', " "CaseDescription=`'$tCaseDescription`', "
        "CaseLang=`'$tCaseLang`' " "WHERE Id=$tCaseId;"
    )

    try{
        $result = Invoke-SqlUpdate -ConnectionName 'maria' -Query $qUpdate
      } catch {
        Write-Host -Foreground Red -Background Black ("result: " + $result + ' / No. ' + $i)
        #break
      }
}

Close-SqlConnection -ConnectionName 'maria'

유감스러운 구문 강조에 대해 사과드립니다. 스크립트 블록에는 SQL, Powershell 및 Python이 포함됩니다 .. 🙄