2021年4月24日 星期六

PowerShell 如何輸出 不帶BOM的 UTF-8 檔案

PowerShell 如何輸出 不帶BOM的 UTF-8 檔案

本文的說明都是針對 PowerShell 5.1 版本,從6版本開始就沒這個問題了,直接 Out-File 就是 UTF8 了。

這個問題很坑,現在版本的Windows10還在用舊版的 PowerShell 5.1 導致輸出UTF8檔案的時候是帶有BOM的。

個人認為最好的解決辦法是直接更新到 PowerShell7.0 就直接什麼問題都沒有了,預設輸出就是不帶BOM的UTF8。



解決方案1 - ASCII

簡單粗暴的的方法,直接寫入 ASCII 就好了。
不能寫中文,但可以記下來應急的時候很好用。

$FileContent = "only english content"
$dstPath = [Environment]::GetFolderPath("Desktop")
$FileContent | Out-File -Encoding ASCII "$dstPath\utf8.txt"

解決方案2 - WriteAllLines

這個方法就不會出問題了,不過是從記憶體一次寫入大小有限。

$FileContent = "中文UTF8內容"
$dstPath = [Environment]::GetFolderPath("Desktop")
$Enc = (New-Object System.Text.UTF8Encoding $False)
[System.IO.File]::WriteAllLines("$dstPath\utf8.txt", $FileContent, $Enc);

解決方案3 - Out-FileUtf8NoBom

這個函式比較能從根本解決問題

載入很簡單,直接打這行就可以把方法載入了

irm https://gist.github.com/mklement0/8689b9b5123a9ba11df7214f82a673be/raw/Out-FileUtf8NoBom.ps1 | iex

再來使用方法和原本的 Out-File 差不多 (不過原作者並沒有實現全部的接口)

$FileContent = "中文UTF8內容"
$dstPath = [Environment]::GetFolderPath("Desktop")
$FileContent | Out-FileUtf8NoBom $dstPath\utf8.txt

這樣就能簡單生成一個不帶BOM的UTF8文件了。

原作者:https://gist.github.com/mklement0/8689b9b5123a9ba11df7214f82a673be


 

如何安裝 Out-FileUtf8NoBom 到電腦上

上面的指令關掉之後副程式就沒了,每次都要重新載入,如果想要用久的安裝到電腦上,可以使用下面指令。

# 調整權限
Set-ExecutionPolicy RemoteSigned -scope CurrentUser

# 創建初始化腳本檔
if (!(Test-Path -Path $PROFILE )) { New-Item -Type File -Path $PROFILE -Force }

# 從gist載入函式
irm https://gist.github.com/mklement0/8689b9b5123a9ba11df7214f82a673be/raw/Out-FileUtf8NoBom.ps1 | iex

# 把函式寫入腳本檔
"`nfunction Out-FileUtf8NoBom {`n${function:Out-FileUtf8NoBom}`n}" | Out-FileUtf8NoBom -Append $PROFILE

這樣就永久安裝到電腦了,隨時都可以用 Out-FileUtf8NoBom 函式了


其他編碼轉換

其他編碼的轉法可以參考這裡的查詢方法

# 直接存取只有預設與UTF8 (可以按TAB一個一個查)
 [Text.Encoding]::Default
[Text.Encoding]::UTF8

# 用名稱查詢
[Text.Encoding]::GetEncoding('UTF-8')
[Text.Encoding]::GetEncoding('BIG5')
[Text.Encoding]::GetEncoding('Shift_JIS')

# 用編號查詢
[Text.Encoding]::GetEncoding(65001)
[Text.Encoding]::GetEncoding(950)
[Text.Encoding]::GetEncoding(932)

實際使用時像這樣

# 編碼
$Enc_Default = [Text.Encoding]::Default
$Enc_UTF8_BOM = [Text.Encoding]::GetEncoding(65001)
$Enc_UTF8 = New-Object System.Text.UTF8Encoding $False
$Enc_BIG5 = [Text.Encoding]::GetEncoding(950)
$Enc_SIFT = [Text.Encoding]::GetEncoding(932)

# 讀寫檔案
$Encoding = $Enc_UTF8
[System.IO.File]::ReadAllLines($Path, $Content, $Encoding);
[System.IO.File]::WriteAllLines($Path, $Content, $Encoding);


UTF8 比較特別要用別的寫法否則會變成帶有BOM的檔案,或者是直接在WriteAllLines() 函數上省略最後面的 $Encoding 參數也可以。



參考

2021年4月22日 星期四

PowerShell 如何把資料夾特定檔案 轉換至 UTF8 編碼

PowerShell 如何把資料夾特定檔案 轉換至 UTF8 編碼

tags: 部落格文章

會寫這篇是因為正在學得教程是中國來的 Eclipse 載入之後直接給我亂碼,要一個檔案慢慢添加個別轉換有夠煩的~

程序也搞了好久,先說 PowerShell5.1 不好用建議乖乖去載 PowerShell7.0來用。

順帶一提要解決 PowerShell5.1 用Out-File輸出帶BOM的問題,把編碼改成 ASCII 或是留空都不要打預設就好了。

本篇範例是用 PowerShell7.0 如果你的環境只能用5.1版,上面提到的在自己測試一下。

更新PS7

懶人包打指令,會自動跳出安裝檔一路下一步到底就好

iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI -Quiet"

或是下面官網手動下載,拖下去之後就有載點了
https://github.com/PowerShell/PowerShell

檔案轉換核心代碼

最關鍵的地方就是這裡啦,有點長我用個變數拆兩段,他是可以合體的。(合體之後記得前面整串$ct要括號起來)

    # 要被轉換的檔案位置與編碼
    $En1=GBK
    $F1='z:\file.txt'
    # 轉換後的檔案位置與編碼
    $En2=UTF8
    $F2='z:\file_UTF8.txt'

    $ct = Get-Content -Encoding $En1 $F1
    $ct | Out-File -Encoding $En2 $F2

這樣執行就可以轉換了,F2可以設置成跟F1 一樣就變成同一份檔案直接轉,但是非常不建議這樣做。不小心連續執行兩次檔案就毀了。

檔案編碼似乎是不能查的,我不知道為什麼用VScode這種近代的編輯器是怎麼偵測的,估計是演算法吧?因為也會有錯的時候所以肯定不是寫在檔案上。

寫成一行的範例

這邊還是要再提一次,當前版本的Windows只有內建的PS版本只到5還沒更新到PS7,PS7要自己手動下載,並且手動打開不會更新到系統上。

範例是把一個在Z:\的檔案 File.java,由 GBK 轉換到 UTF8

(Get-Content -Encoding GBK 'Z:\File.java') | Out-File 'Z:\File_UTF8.java'

要是跳出下面訊息
Get-Content : 無法繫結 ‘Encoding’ 參數。無法將 “GBK” 值轉換為 “Microsoft.PowerShell.Commands.FileSystemCmdletProv
單純只是舊版沒支援GBK而已,去升級吧XD。

批量轉換

這個大概是大家最關心的部分,整個把整資料夾裡面有 java 的全部都抓出來轉換到另一個目錄。

這次第一版半成品,那時候沒查到怎麼處理字串建資料夾的方式有點多此一舉。舊版本就不砍了,有需要功能完善的版本從底下連結自取。

## 初版
function  CovertFileEncoding_basic ($F1, $En1, $En2, $F2){
    $ct = (Get-Content -Encoding $En1 $F1)
    $ct | Out-File -Encoding $En2 -FilePath $F2
}
function  CovertFileEncoding ($F1, $En1, $En2, $F2, $tempPath){
    if (!(Test-Path -Path $tempPath)) {
        Write-Warning "暫存資料夾不存在"
    }
    $F2=$tempPath+$F2
    mkdir $F2 | Out-Null
    Remove-Item $F2
    CovertFileEncoding_basic $F1 $En1 $En2 $F2
}

# 轉換FilePath目錄下的所有java檔案,到tempPath目錄
function CovertDirEncoding($FilePath, $tempPath, $go=0) {
    $list = Get-ChildItem -Path $FilePath -Recurse -Filter *.java
    $listN = Get-ChildItem -Path $FilePath -Recurse -Filter *.java -Name
    for ($i = 0; $i -lt $list.Count; $i++) {
        $F1=$list[$i].FullName
        $F2=$listN[$i]
        if ($go -eq 0) {
            $F2
        } elseif ($go -eq 1){
            CovertFileEncoding $F1 "GBK" "UTF8" $F2 $tempPath
        }
    }
}

$FilePath = 'C:\Users\hunan\Desktop\JavaWeb\25\hotel2\src\'
$tempPath = 'Z:\temp\'
CovertDirEncoding $FilePath $tempPath

完整版

GitGub: https://github.com/hunandy14/DirCoverToUtf8

最底下的部分

# 路徑
$FilePath = "Z:\SourceCode\31"
$TempPath = $PSScriptRoot
cd $PSScriptRoot

預設只要更改 $FilePath 即可

變數 $TempPath 是複製的目的地,預設會自動建立在批次檔旁邊。
最後第三行單純只避免寫代碼的時候出事把目錄調到當前

詳細說明以後再寫,覺得八成還會再大動版本,還沒想到怎麼輸入排除檔案跟轉換檔案的方式。有寫應該會先更新在 github 上。

2021年4月20日 星期二

C++ C 如何獲取命令列的響應後返回的字串

[C/C++] 如何獲取命令列的響應後返回的字串

tags: 部落格文章

這邊以檔案目錄為範例,直接獲取檔案目錄中的字串

核心程式

首先最關鍵的函式是這個,這個可以直接獲取響應的命令

#include <io.h>
vector<string> exec(const string& cmd) {
    FILE* pipe = _popen(cmd.c_str(), "r");
    if (!pipe) throw runtime_error("cmd error");
    vector<string> list;
    for (char buffer[256]=""; !feof(pipe);) {
        if (fgets(buffer, sizeof(buffer), pipe) != NULL and buffer[0]!=0) {
            buffer[strlen(buffer)-1] = 0;
            //cout << buffer << endl;
            list.emplace_back(buffer);
        }
    }
    _pclose(pipe), pipe=nullptr;
    return list;
}

使用範例如下

// 獲取當前目錄文件
auto v = exec("dir /b");
for (auto&& i : v) cout << i << endl;

如此一來就對自己打印出當前目錄的文件了
VS2019預設位置是在該cpp文件的目錄,可以找到cpp文件本身



進階使用範例 - getList

再來是比較進階的,把讀取檔案目錄的cmd代碼整合在一起
寫成簡單易用的函式。預設自動封裝成類別可以直接用

#include <iostream>
#include <vector>
#include <io.h>
#include <direct.h>
using namespace std;
//====================================================
class FileList {
public:
    FileList() = delete;
public:
    // 輸入命令並回存到vector陣列
    static vector<string> exec(const string& cmd) {
        FILE* pipe = _popen(cmd.c_str(), "r");
        if (!pipe) throw runtime_error("cmd error");
        vector<string> list;
        for (char buffer[256]=""; !feof(pipe);) {
            if (fgets(buffer, sizeof(buffer), pipe) != NULL and buffer[0]!=0) {
                buffer[strlen(buffer)-1] = 0;
                //cout << buffer << endl;
                list.emplace_back(buffer);
            }
        }
        _pclose(pipe), pipe=nullptr;
        return list;
    }
    // 獲取清單(檔案+資料夾)
    static vector<string> getList(string path="", string param="") {
        const string sp=" ";
        string cmd = "dir";
        for (size_t pos=0; (pos=path.find("/")) != string::npos;)
            path.replace(pos, 1,"\\");
        auto&& cwd = exec("cd"); // 紀錄初始位置並前往
        if (path != "" and _chdir(path.c_str()))
            throw runtime_error("dir is no exist.");
        if (param == "") // 查詢結果
            param = "/a-s/b/s" + param;
        cmd = cmd + sp + param;
        auto&& result = exec(cmd);
        if (_chdir(cwd[0].c_str())) // 返回初始位置
            throw runtime_error("dir is no exist.");
        return result;
    }
    // 獲取檔案清單
    static vector<string> getFileList(const string& path, const string& param="") {
        return getList(path, "/a-d-s/b/s/on " + param);
    }
    // 獲取資料夾清單
    static vector<string> getDirList(const string& path, const string& param="") {
        return getList(path, "/ad-s/b/s/on " + param);
    }

};

void test_exec(const string& path) {
    cout << "------------------test_exec-------------------" << endl;
    if (path != "" and _chdir(path.c_str())) {
        throw runtime_error("dir is no exist.");
    } 
    auto v = FileList::exec("dir /a-d/b/s/on *.*");
    for (auto&& i : v) {
        cout << i << endl;
    }
    cout << "---------------------------------------------" << endl;
}
void test_getList(const string& dir) {
    vector<string> v;
    // 第一個參數是路徑,留空預設是當前工作目錄
    cout << "----------------test_getList-----------------" << endl;
    v = FileList::getList(dir);                    // 獲取檔案清單(資料+檔案)
    for (auto&& i : v) cout << i << endl;
    cout << "---------------------------------------------" << endl;
    v = FileList::getFileList(dir);            // 獲取檔案(不包含資料夾)
    for (auto&& i : v) cout << i << endl;
    cout << "---------------------------------------------" << endl;
    v = FileList::getDirList(dir);                // 獲取資料夾(不包含檔案)
    for (auto&& i : v) cout << i << endl;
    cout << "---------------------------------------------" << endl;
    v = FileList::getFileList(dir, "*.txt");    // 獲取特定檔案
    for (auto&& i : v) cout << i << endl;
    cout << "---------------------------------------------" << endl;
}
//============================================================================
int main(int argc, char const* argv[]) {
    const char* dir0 = "";
    const char* dir = "Z:\\a";
    test_exec(dir);
    test_getList(dir);
    return 0;
}
//============================================================================

2021年4月19日 星期一

Win10 不開機 EFI 開機檔損毀如何修復

Win10 不開機 EFI 開機檔損毀如何修復

通常症狀會出現以下錯誤信息

reboot and select proper boot device

不過這個信息導致的原因有很多,這邊只是說明其中的一個,開機檔損毀怎麼修復。


步驟:
1. 插入Win10 安裝USB
2. 然後按修復
3. 移除USB重新啟動


2021/09/23
後來看到有人提才想起來,內建修復修是包含開機磁區修復的。當初寫這篇的時候沒想到,底下是舊文,想自己折騰一下如何手動修復可以看看。

修復沒修好的話估計是有其他狀況了,具體情況要實際看看才知道。然後內建自帶的是修復開機磁區,並不會重建。如果你是拔掉其他硬碟導致不開機,八成是當初安裝的時候沒拔舊硬碟,導致開機磁區在舊硬碟上,這種情況在新硬碟上只能自己手動重建,參考底下文章說明。

修復的邏輯是先找同一顆硬碟上的EFI分區,沒有的話就找別的硬碟。為什麼沒拔舊硬碟會造成這個狀況,是因為在創建分區的時候如果已經存在EFI分區(包含其他硬碟)則不會自動建立,然後安裝完成後會自動執行一次修復,開機磁區就裝到其他硬碟上了。




重建EFI

重建EFI有很多方式,用軟體是最快的,一個按鍵就修好了
不過相對也麻煩還要多載一個軟體下來。
如果不想抓軟體也可以試試下面還有用命令修復的方法。


備註:有一種情況是連EFI分區都沒有。通常當初安裝Windows的時候沒把舊硬碟拔掉,導致啟動分區在舊硬碟上,新硬碟壓根就沒有。這種情況先跳到最後面看。


使用軟體修復EFI

首先你得找到另一台電腦,把硬碟拔過去插著用Win10開機
然後載這個軟體 Dism++:https://www.chuyu.me/zh-Hant/

再來方法很簡單,打開之後先選要修復的槽位,點一下
(你插到別台電腦就一定不是C曹歐,不要被我的是意圖干擾了)


然後選擇修復


OK修好了。

底下是另一個用命令修復的方法,這邊如果修好了就別再往下做了。





替EFI磁碟新增磁碟代號

首先你得找到另一台電腦,把硬碟拔過去插著用Win10開機。或者是利用隨身碟安裝PE插進壞掉的目標電腦然後開機用命令修復。

開準備好環境之後就可以開始拉,滑鼠移動到開始,對著微軟旗幟按右鍵,打開Powershell


打開之後輸入

DISKPART

再來會進入該程序,接著輸入

list disk

這個步驟是查詢你的硬碟在哪個曹
以我的圖來說我要修的硬碟是894GB的那顆,就選那顆對照前面編號

sel Disk 1

接頭要查詢 EFI 分區在哪裡

list part

EFI 分區他的 Type 處會寫著 Systme 選擇那個分區
以我的圖來說在分區2 (通常會在分區1)

sel Parttition 2

再來就可以替他新增磁碟機代號了

assign letter=Y




最後記得離開 DiskPart 
exit






修復EFI分區

好了之後就可以繼續下一步修復分區了,只剩一行指令了。
重新開一個新的 Powershell

然後輸入

# E = 目標硬碟的系統槽, Y = 目標硬碟的EFI槽
bcdboot E:\windows /f UEFI /s Y:\ /l zh-tw

上面記得槽位要選對,不確定的去我的電腦裡面看一下確認,然後就修好瞜~


然後你可以回到剛剛讓你留著的視窗重新進入 diskpart 後輸入

# 取消掛載的EFI磁碟代號
remove letter=Y





新增EFI分區

如果你是連EFI分區都沒有的,整個硬碟只有一個曹上面指令會做不了,要先把EFI分區弄出來。

dikspart

# 查看硬碟
list disk

# 選擇目標硬碟
sel disk 1

# 查看分區
list part

# 選擇分區
sel Partition 1

# 壓縮該分區
shrink disired=300

# 創建EFI分區
create partition efi size=100

# 格式化EFI分區
format quick fs=fat32 label="System"

# 指定EFI分區磁碟代號
assign letter=Y

再來接著"修復EFI分區"的標題那部分即可

2021年4月17日 星期六

PowerShell 安裝主題讓 終端機更好讀

PowerShell 安裝主題讓 終端機更好讀

廢話不多說先上圖



推下一行比較好讀一行是這一次執行命令的,哪一個行開始是上一次命令的,常常看半天。有了這麼粗一條的主題之後就不會有問題了。

唯一讓我覺得是不好的地方是列印內建的項目的時候,項目和項目中間每一行都會多空行,導致看起來很零散要捲很久一直看下去。這個我覺得是蠻糟糕的設計,有要瀏覽檔案我會把主題關掉。



安裝主題

設置權限

在開始之前需要先設置Powershell的權限,設置之後才能執行安裝的指令。
輸入之後需要手動輸入 Y 並按下 Enter 才能繼續。

Set-ExecutionPolicy RemoteSigned -scope CurrentUser

安裝主題套件

主題套件有兩個,輸入之後需要手動輸入 Y 並按下 Enter 才能繼續。

Install-Module posh-git
Install-Module oh-my-posh

執行主題

到這邊就已經好了,再來只需要啟動主題就好。

Import-Module posh-git
Import-Module oh-my-posh
Set-PoshPrompt -Theme Paradox


如何自動開啟

剛剛的主題雖然是啟動成功了,但不過這只有對當前視窗有效,關閉重開一個就沒了。如何自動開啟需要自己配置啟動設置檔。

新增配置文件

輸入下面指令如果沒有建立過則自動建立新檔案,建立完畢之後會自動打開記事本。如果已經有配置檔存在,就會直接打開文件。(不小心關了再打一次就好)

if (!(Test-Path -Path $PROFILE )) { New-Item -Type File -Path $PROFILE -Force }notepad $PROFILE

然後在記事本裡面輸入下面的內容,並存檔就可以關閉了。

# 自動配置主題Set-PoshPrompt -Theme Paradox}
# 路徑預設在桌面
cd ~\Desktop\



字體調整 - 更紗黑體

再來要處理的就是醜醜的新細明體,這個真的醜到不行。

推薦安裝 更紗黑體 下面是載點。
點進去之後下載TTC的版本就可以了,下載下來解壓縮後直接雙擊安裝。

https://mega.nz/file/s8tkxBwI#__XbfNawXftWIdoNzJvC_DqZawPFhBJEruL25Dbw714

安裝好之後就開始吧。重新開啟一個 PowerShell 視窗

對著最上面的地方按右鍵內容



調整一下字體。記得要選”等距”,因為是ttc包也同時安裝了沒”等距”的字型


再來色彩的部分建議調整成灰底比較順眼些,灰底的RGB值3個都是30
底下的透明度要不要調整可以看個人(可以偷看背景的指令邊看邊打)