2023年4月29日 星期六

正則表達處理路徑 獲取檔名或副檔名

正則表達處理路徑 獲取檔名或副檔名

  1. 為了適應 Windows 和 Unix 類操作系統的路徑,使用了 [\/] 來匹配路徑分隔符,這樣可以同時匹配正斜杠(/)和反斜杠(\)。
  2. 正則表達式仍然可能在一些極端情況下失效,例如檔案名稱中含有正則表達式特殊字符等。



正則表達式

這邊用 PowerShell 當範例,打開之後直接貼上就能測試了。

範例路徑

$path = "C:\Users\Username\Documents\example.txt"
$path = "C:\Users\Username\Documents\.git\example"
$path = "C:\Users\Username\Documents\.git\example.fix.txt"
$path = "C:\Users\Username\Documents\.git\.gitignore"

這邊多給兩個比較機車的情況當測試樣本。


檔案名稱

# 使用 -replace 運算符和正則表達式來獲取檔案名稱
$filenameWithoutExtension = $path -replace '^(.*[\\/])([^\\/]+?)(\.[^\\/.]+)?$', '$2'

完整檔案名稱

# 使用 -replace 運算符和正則表達式來獲取檔名(包含副檔名)
$filenameWithExtension = $path -replace '^(.*[\\/])'

副檔名

# 使用 -replace 運算符和正則表達式來獲取副檔名
$extension = $path -replace '^.*[/\\][^/\\]*?(\.[^/\\.]*)?$','$1'
$extension = ($path -replace '^.*[/\\]([^/\\]*)$', '$1') -replace '^.*?\.([^.]*)$|^.*$', '$1'

父資料夾名

# 使用 -replace 運算符和正則表達式來獲取父資料夾名稱
$parentFolderName = $path -replace '^(.*[\\/])?([^\\/]*)[\\/]([^\\/]+)$', '$2'

檔案所在路徑

# 使用 -replace 運算符和正則表達式來獲取檔案所在路徑
$filePath = $path -replace '[\\/]([^\\/]*)$'


 

在 PowerShell 中其實有提供以上需求的C#函式,如果不是舊版沒支援建議是直接用內建函式比較妥,至少有保證不會出事。



2023年4月23日 星期日

MSSQL 如何透過 PowerShell 上傳 CSV 到資料庫上

MSSQL 如何透過 Bat 上傳 CSV 到資料庫上

能夠上傳CSV的指令微軟內建有兩個 bcp.exe 跟 BULK INSERT,不過後者是寫在sql中的代碼有個很致命的缺點是只能上傳HOST端上的檔案,無法從其他電腦上傳過去。

還有一點要注意的是雖然文章是寫CSV,不過準確地來講是DATA,內容不能包含檔頭與頭尾雙引號。不能包含是因為他就按照那個樣子傳上去,到時候資料庫上會看到全部都帶有雙引號,也因為沒有雙引號保護的關係,是無法上傳逗號上去的,會被當作下一份資料的分割。




BULK INSERT 的上傳方法

雖然已知這方法有個根本上的致命問題,不過還是筆記一下紀錄,或許會在哪裡用到。要解決這個致命問題其實有另一個解法是把CSV上傳到HOST也能讀取的SAMA上就能傳了。

這個方法好處大概就是可以跳過檔頭吧,代碼中的 “FIRSTROW = 2” 指的是從第二行開始上傳。

$serverName = "localhost"
$databaseName = "CHG"
$userName = "kaede"
$password = "1230"

$csvFilePath = "\\ORACLE-SV\Source\csvfile.csv"
$schemaName = "CHG"
$tableName = "Employees"
$Table = "[$databaseName].[$schemaName].[$tableName]"

$query = @"
BULK INSERT $Table
FROM '$csvFilePath'
WITH
(
    FORMAT = 'CSV',
    FIRSTROW = 2
);
"@

sqlcmd -S $serverName -d $databaseName -U $userName -P $password -Q $query

-




BCP 的上傳方法

因為檔案是要讀取純資料的關係,這邊也寫了一個函式自動處理CSV的轉換。

function Export-CustomCSV {
    param (
        [Parameter(ValueFromPipeline = $true)]
        [psobject[]]$InputObject,
        [string]$InputPath,
        [Parameter(Mandatory = $true)]
        [string]$OutputPath,
        [string]$Delimiter = ',',
        [System.Text.Encoding]$Encoding = (New-Object System.Text.UTF8Encoding $False),
        [switch]$SkipHeader
    )

    begin {
        $writer = New-Object System.IO.StreamWriter -ArgumentList $OutputPath, $false, $Encoding
        $headerProcessed = $false
    }

    process {
        if ($InputObject) {
            foreach ($obj in $InputObject) {
                $line = ""
                $properties = $obj | Get-Member -MemberType Properties
                foreach ($prop in $properties) {
                    if (-not [string]::IsNullOrEmpty($line)) {
                        $line += $Delimiter
                    }
                    $value = $obj.$($prop.Name) -replace '"', '""'
                    $line += $value
                }
                $writer.WriteLine($line)
            }
        } elseif ($InputPath) {
            $reader = New-Object System.IO.StreamReader -ArgumentList $InputPath, $Encoding
            while (-not $reader.EndOfStream) {
                $line = $reader.ReadLine()
                if ($SkipHeader -and -not $headerProcessed) {
                    $headerProcessed = $true
                    continue
                }
                $fields = $line.Split($Delimiter)
                $newLine = ""
                for ($i = 0; $i -lt $fields.Length; $i++) {
                    $cleanField = $fields[$i].Trim('"')
                    if ($i -gt 0) {
                        $newLine += $Delimiter
                    }
                    $newLine += $cleanField
                }
                $writer.WriteLine($newLine)
            }
            $reader.Close()
        }
    }

    end {
        $writer.Close()
    }
}

接下來是上傳的部分

$sourceCsv = 'input.csv'  # 更改為您的源 CSV 文件路徑
$destinationCsv = 'output_existing.csv'  # 更改為您的目標 CSV 文件路徑
Export-CustomCSV -InputPath $sourceCsv -OutputPath $destinationCsv -SkipHeader

$serverName   = "localhost"
$databaseName = "CHG"
$userName     = "kaede"
$password     = "1230"
$schemaName = "CHG"
$tableName = "TEST"
$Table = "[$databaseName].[$schemaName].[$tableName]"

$csv = $destinationCsv
$output = & bcp $Table in $csv -c -t ',' -r "\n" -S $serverName -U $userName -P $password
$hasError = $false
$numRowsCopied = 0

$outputString = $output -join "`n"
if ($outputString -match "(\d+) rows copied\.") {
    $numRowsCopied = [int]$matches[1]
    if ($numRowsCopied -eq 0) {
        $hasError = $true
    }
}

if ($hasError) {
    Write-Host "BCP 命令執行失敗,錯誤信息:"
    Write-Host $outputString
} else {
    Write-Host "BCP 命令執行成功,共複製了 $numRowsCopied 行"
}

-


追加寫完之後發現 PowerShell 還有一個工具 System.Data.SqlClient.SqlConnection 這個應該才是標準解,專門為編程而生的,搞定了再放上來。

2023年4月11日 星期二

正則如何抓出所有雙引號並修改特定字串

正則如何抓出所有雙引號並修改特定字串

這個問題之前看PTT版討論可能好像是無解,不過今年因為GPT出了正則變得更容易使用了,不需要花大量時間,少樣本學習直接丟就有答案了。

這邊用的語言是 Powershell 可以直接打開終端機輸入就可以驗證了



匹配雙引號中的字串

先來個範例樣本

$csv_string = '"a", "b", "c"'

對應的代碼是

$csv_string = '"a", "b", "c"'
$pattern = '(?<=\")[^"\s]*(?=\")'
$matches = [regex]::Matches($csv_string, $pattern) | ForEach-Object { $_.Value }

$matches

如此一來就可以取出 abc 的陣列了,後面的 ForEach-Object { $_.Value } 只是把regex物件中的字串給抓出來而已


不過這有個問題如果字串是相連的 '"dd1"ffff"dd2"' 會抓到中間值,抓出三個連帶ffff也抓了。對於這個的解法是

$csv_string = '"dd1"ffff"dd2"'
$pattern = '(?<=\")[^"\s]*?(?=\"(?:[^"]*"[^"]*")*[^"]*$)'
$matches = [regex]::Matches($csv_string, $pattern) | ForEach-Object { $_.Value }

$matches

這樣可以準確避開了,不過我想如果情況再複雜一點可能也會出bug,在長下去也沒意義了,太長了無法閱讀之外也容易埋雷,依照情況適當選用就好。



匹配雙引號中的字串並取代替特定字串

進階一點換一個範例,這次除了要抓出雙引號之外還附加要修改雙引號內特定的字串

$csv_string = '"ahuchgnde@#", "gokerjorb", "eokgchgjoec", chg, "chg"'

以這個字串來說我要把chg改成[CHG],並且設置了檢查項目其中有一個chg是不帶雙引號的

$csv_string = '"ahuchgnde@#", "gokerjorb", "eokgchgjoec"'
$pattern = '((?<=")[^"]*?)chg(([^"]*?)(?="))'
$replacement = '$1[CHG]$3'

$new_csv_string = $csv_string -replace $pattern, $replacement
$new_csv_string



匹配雙引號中的雙引號

難的地方在於雙引號沒辦法區分頭尾,雖然用看得看的出來但是真的要寫實在是無從下手。

經過反覆確認如果是沒有給定條件是判斷不出來的,這邊用CSV的文本當範例可以用逗號輔助判斷頭尾雙引了,沒這逗號replace是做不到的。

$csv_string = 'some data,"AAA""這裡""AA", "BBBBBB",more data,"", "CCCDDD""EEE"""'

以這個範例來說我要消除雙引號中的雙引號,消除到只剩一個

$csv_string = 'some data,"AAA""這裡""AA", "BBBBBB",more data,"", "CCCDDD""EEE"""'
$regex = '(?<=[^,])""(?=[^,])'
$csv_string = $csv_string -replace $regex, '"'
$csv_string

如此一來就可以抓出來了,如果要完全消除就把後面的雙引號留空白就好

不過這還是有個小問題是如果引號中的引號不是兩個一組出現,就無法處理了。試了很久沒出來估計是正則做不到了。




2023年4月9日 星期日

VMware 出現 侧通道缓解 的錯誤信息

VMware 出現 侧通道缓解 的錯誤信息

信息是:

您在运行该虚拟机时启用了侧通道缓解。侧通道缓解可增强安全性,但也会降低性能。
要禁用缓解,请在虚拟机设置的“高级”面板中更改侧通道缓解设置。有关更多详细信息,请参阅 VMware 知识库文章 79832,网址为

會出現這個是因為安裝了 docker 被打開系統中的虛擬機功能導致的,雖然有個解決辦法是依照網址操作把那功能給官掉就好了,不過虛擬機性能變的超級差,最後還是得復原才能正常。

關掉的方法是先移除 docker 然後在確保這兩個功能關閉即可。



如果電腦也有開 wsl 記得要轉回來 ws1 不然可能會無法使用

wsl --set-version Ubuntu-20.04 1


還有一個問題是這頓操作過程可能會新增一個 Hyper-V 的虛擬網卡出來,會導致VM預設去讀那張卡,因為已經刪掉docker並且關掉虛擬功能了,那張卡會沒網路。

需要手動那張卡刪除或是從VM的設定中選中自己的自己網卡,不然自動狀態會優先讀 Hyper-V 的網卡導致梅網路。



2023年4月5日 星期三

JavaScript 轉譯 XML 特殊符號

JavaScript 轉譯 XML 特殊符號

因為只有5個而已沒有線程的函式庫可以用,自己刻了一個又覺得直接替換好像有點太簡單,姑且看了一下怎麼用一張映射表來轉譯。

這個可以透過修改映射表任意變更對應的關係,相對來說應用在別的對方要修改可能會方便不少。


代碼

先上最簡單的直接替換掉

// 將實體字符轉換為特殊字符
const encodeXml = str => str.replace(/&/g, '&amp;')
                            .replace(/</g, '&lt;')
                            .replace(/>/g, '&gt;')
                            .replace(/"/g, '&quot;')
                            .replace(/'/g, '&apos;');

// 將特殊字符轉換為實體字符
const decodeXml = str => str.replace(/&amp;/g, '&')
                            .replace(/&lt;/g, '<')
                            .replace(/&gt;/g, '>')
                            .replace(/&quot;/g, '"')
                            .replace(/&apos;/g, "'");

再來這個是統一拉到映射表理管理的做法

// 映射表
const entitiesMap = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&apos;',
}; const rvEntitiesMap = Object.fromEntries(Object.entries(entitiesMap).map(([k, v]) => [v, k]));

// 將實體字符轉換為特殊字符
const encodeXml = str => {
  const regex = new RegExp(`[${Object.keys(entitiesMap).join('|')}]`, 'g');
  return str.replace(regex, m => entitiesMap[m]);
};
// 將特殊字符轉換為實體字符
const decodeXml = str => {
  const regex = new RegExp(`(${Object.keys(rvEntitiesMap).join('|')})`, 'g');
  return str.replace(regex, m => rvEntitiesMap[m]);
};

// 測試
const text = '<root><node id="1">Hello & World</node></root>';
const encodedText = encodeXml(text);
const decodedText = decodeXml(encodedText);
console.log('原始文本:', text);
console.log('編碼後文本:', encodedText);
console.log('解碼後文本:', decodedText);

結果

原始文本: <root><node id="1">Hello & World</node></root>
編碼後文本: &lt;root&gt;&lt;node id=&quot;1&quot;&gt;Hello &amp; World&lt;/node&gt;&lt;/root&gt;
解碼後文本: <root><node id="1">Hello & World</node></root>




版本問題

反轉哈希表的部分需要ES2019才能跑,不能跑可以替換成這個舊版的寫法

const rvEntitiesMap = Object.entries(entities).reduce((acc, [k, v]) => {
  acc[v] = k;
  return acc;
}, {});

-