PowerShell 당 Python 스크립트를 호출하고 PSObject를 전달하고 구문 분석 된 데이터를 반환합니다.
몇 가지 배경 : 현재 내가 가진 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에서 처리 된 후 반환하는 것입니다.하지만 어떻게 할 수 있는지 모르겠습니다. 파이썬 / 파이썬 함수로 이것을 실현하십시오.
귀하의 접근 방식 / 제안, 기타 아이디어는 무엇입니까? 감사 :)
답변
다음 간단한 예제는 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') + ']')
'@
@ 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이 포함됩니다 .. 🙄