2024年6月6日 星期四

PowerShell 通配符 Get-ChildItem 無法匹配帶有方角括號[] 名稱的檔案

PowerShell 通配符 Get-ChildItem 無法匹配帶有方角括號[] 名稱的檔案

2024-06-06

這算是 PowerShell 5.1 預設給的方便的,不過在不知情的情況下就造成困擾了

假設有一個 File[1].txt 的檔案,於是就這樣獲取

Get-Item File[1].txt

居然什麼都沒有,於是乎聰明如你,用引號總能解吧?

Get-Item "File[1].txt"
Get-Item 'File[1].txt'

想不到吧很遺憾還是沒有辦法

這東西叫做通配符,內建在 Get-Item 裡的所才沒法通過引號處理的
about Wildcards - PowerShell | Microsoft Learn


2025-02-20 這是一個多重buf疊加的問題

第①個原因是上面的方括號被當作萬用字符對待了,解法就是上跳脫字元

Get-Item "File``[1``].txt"
Get-Item 'File`[1`].txt'

引出我們第②個問題,雙引號會進行一次 "解釋"
也就是會解析跳脫字元所以如果只用雙引號記得要雙重引號

再來引出第③個問題,Get-Item 並不支援萬用字元 * 或 ?
導致如果你要合著用就用不了,你只能選擇使用 Get-ChildItem 函式了

但是這 Get-ChildItem 的預設參數 -Path 自帶一次解析,這和Get-Item有區別
也就是你得這樣 (這個問題在 Pwsh7 中已經被修復不需要雙重反引號)

Get-ChildItem -Path "File``````[1``````].txt"
Get-ChildItem -Path 'File```[1```].txt'

很礙眼的三重引號出現了

為什麼會這樣呢,因為你無法區分那方括號到底是檔名還是萬用字元

舉例子來說檢索條件是 File[0-1].txt ,那你期望搜倒的檔案到底是哪個呢?

  • File[1].txt
  • File1.txt


再來是第④個問題,那反引號是合法檔名
舉例子來說檢索條件是 File`[1`].txt ,那你期望搜倒的檔案到底是哪個呢?

  • File`[1`].txt
  • File[1].txt

你把第一個 File`[1`].txt 複製起來,建立一個文字文件貼上會發現居然是合法檔名

那這樣就完拉,你怎麼分區,就只能是三重反引號去多解析一次了
這就是為什麼裡面會自帶一次解析雙引號的理由

  • Get-Item 'File```[1```].md' ->檢索到-> File`[1`].md
  • Get-Item 'File`[1`].md' -> 檢索到 -> File[1].md
Get-ChildItem 擇要雙倍
  • Get-ChildItem 'File``````[1``````]' ->檢索到-> File`[1`].md
  • Get-ChildItem 'File``[1``].md' -> 檢索到 -> File[1].md

(我知道 Get-ChildItem 'file`[1`].md' 可以找到 File`[1`].md 這個應該是 bug ,
因為 Get-ChildItem 'file`[[0-1]`].md' 找不到 
File`[1`].md )


一個簡易解法是把問題③處理掉,自己多加一層反引號

Get-ChildItem -Path ('File`[1`].txt' -replace('`','``'))

這樣就能應付掉那個不合協感了


最後一個第⑤個問題算是比較隱形的,剛剛提到的 Get-ChildItem 的 -Path 其實只有最右邊才會自動解析兩次,比如說 C:\folder\sub1\sub2 只有 sub2 的位置才能用雙重反引號解這個問題。

D:\test\Test```[0-2`] 可以找到 D:\test\Test`[0-2] 這個帶反引號的資料夾
D:\test\Test```[0-2`]\File.txt 不能找到 D:\test\Test`[0-2]\File.txt 的檔案

解法就是用 -Include 去把檔名移出去,讓父資料夾就被調到最右邊再用雙重反引號處理

Get-ChildItem -Recurse -Path 'D:\test\Test```[0-2`]' -Include 'File.txt'

這個bug導致路徑中的反引號出現在最右邊以外,沒有辦法可以找到該檔案
上面的解法整多就是能解決父資料夾,但是在上去爺資料夾就還是找不到
 (這個問題在 Pwsh7 中已經被修復不需要雙重反引號)

並且其實有個問題是會連帶找到 D:\test\Test`[0-2]\sub\File.txt 子資料夾中同名檔案
這個會引發下一個問題


2025-02-21

發現另一個問題⑥ "gci .\subfolder\file.* -Depth 0" 這裡的 -Depth 0 是無效的...
會搜到子資料夾的同名檔案

可能跟⑤的現象有關,內部實作可能是把最右邊的抓出來單獨解析
跟 Depth 搭配的時候某個環節出錯了,這個也是 pwsh7 已經修復


有個很醜的解法是,搜出來之後再二次過濾把子資料夾濾掉
缺點就稍微浪費性能,斟酌著用吧,編譯器bug真只能妥協了

$dir = Split-Path $path 
$file = Split-Path $path -Leaf
Get-ChildItem -Path $dir -Include $file -Recurse | 
    Where-Object { $_.DirectoryName -eq $dir }















下面是一步一步的推導到底發生什麼了


情況1

如果你只是單純想解決括號問題,沒有要搞通配符號組合,參考這個就能解了。

# 使用函式接口 LiteralPath (就是拒用萬用符拉)
Get-Item -LiteralPath 'Test[1]'.txt

# 連帶反轉意符號一起傳進去
Get-Item '.\Test`[1`].txt'

# 如果用雙引號圍住的話傳入前就會被解掉要多2個反引號
Get-Item ".\Test```[1```].txt"





情況2

另一個坑是當括號與星號一起出現時,又會導致另一個解析問題,括號被當作通佩服一起解釋了,就是你想匹配 Test[*].txt 多個檔案的時候。

這時候情況更複雜了一些,你得連反引號都一起傳進去才能,免得反引號在傳入的當下被解析了少了一次解析。

# 連帶反轉意符號一起傳進去
Get-Item '.\Test``[*``].txt'

# 雙引號的話更精彩一點,堪稱這在衝三小
Get-Item ".\Test`````[*`````].txt"


測試結果




情況3

2024-09-28 發現一個新問題 'Test``[*``].txt' 實際上匹配的並不是 Test[*].txt 而是 Test*.txt,說起來繞口,原因是那個方括號被當作萬用字解讀了。

如何更明白的理解問題可以看下面這個例子

# 匹配檔名為 Test[0].txt, Test[1].txt 的檔案
Get-Item '.\Test``[[0-1]``].txt'



這樣寫出來應該就很好理解了,因為萬用字的匹配是在 cmdlet 裡面做的,所以才導致這個反人類思維的解...

第一層反引號會在傳入的時候被解掉,此時算法實際吃到的是一個反引號的 `[ 所以她成功識別是一個引號,第二個由於就是引號沒什麼好說的吃到引號當作萬用字解讀。

也就是可以理解成 [0-1] 這東西會被當作一個特殊字串對待,剩下的保持原本模樣。

那為什麼 '.\Test`[1`].txt' 能夠被正確識別呢? 因為 [1] 沒有構成合法的萬用字元所以被當作字串解讀了 (這鬼邏輯...雖然是正確的但別這樣設計搞人啊)


情況4

以為這樣就結束了嗎? 不還有一個更鬼畜,記得先溫得好情況3的神奇邏輯,這是同一套邏輯的變態版,現在考慮到檔名含有雙引號的情況

# 匹配檔名為 Test`[0].txt, Test`[1].txt 的檔案
Get-Item '.\Test``````[[0-1]``].txt'



居然是要添加4個引號? 是的這個邏輯的是正確,因為傳入的時候會被吃掉一次只剩2個,真正在運算的時候又被吃掉一次只剩1個,所以正確的識別了

這個鬼邏輯來自於,不知道為啥路徑相關的cmdlet會對路徑做一次多餘的雙引號解析導致的,啥原因設計成這樣就不知道了。



下面是測試的方法,看了應該就秒懂上面的 情況4 發生什麼事情了
 (注意雙引號與單引號)

# 模擬 Get-Item 是如何處理萬用字元的
'.\Test`[1].txt' -like ".\Test``````[[0-1]``].txt"

對他們是相等的...也就是說對於所有路徑相關的 Get-Item, Test-Path 等等必須自己有自覺的認識到,即使我用了單引號避免被解釋,但實務上進去還會被解析一次。


這個真該打屁股了誰寫的反人類設計...

不過嘴砲歸嘴砲,八成是身處歷史當下的那群人,遇到某個無法解決的問題,提出的無可奈何的解吧...。現在好拉已經過這麼久了,想修估計也改不了了,成為萬世毒瘤了


對於這個局面的處置方法

對於這個局面要人性化的處置可以參考這下面這個解法

# 匹配的真實路徑 Test`[1].txt
Test-Path ('Test```[[0-1]`].txt' -replace('`','``'))
Test-Path ('Test```[*`].txt' -replace('`','``'))


這少這樣理解度就高一點了,為什麼會有三個是因為需要反轉意一個反引號跟方括號導致的,是可以讀懂的代碼。




2024-10-09 深挖bug的本質

這就完事了嗎? 並沒有,我又再次深入地去挖掘這個問題,發現老早就一堆人在講了

  1. 反引號轉義不一致 ·問題 #7999 ·PowerShell/PowerShell (github.com)
  2. 工作目錄路徑與相對路徑中的通配符 ·問題 #24260 ·PowerShell/PowerShell (github.com)
  3. [心得] PowerShell 那些惱人的路徑 BUG - 看板 Windows - 批踢踢實業坊


但是呢六年過去還是沒修,八成是因為歷史包袱改不了了。最大的絆腳石應該是因為

永遠都無法區分路徑字串上的 D:\test\Test`[0-2] 反引號到底是跳脫字元還是普通字符。
(但是D:\test\Te`st[0-2] 這個情況就好一點了,可以)


回到剛剛的話題為什麼這事情還沒完是因為,我們已經知道雙重跳脫可以協助我們區分到底誰是字元誰是跳脫

D:\test\Test```[0-2`]

我們上個色就知道誰是跳脫了,看上去很美好對吧,但是並沒有 PowerShell 在遇到反引號加上一個任意字符,再加上一個右方括號的時候,bug就由此而生

我找了很久還是沒找到這到底對應到哪個路徑,最後才發現

D:\test\Test```[0-2`] 可以找到 D:\test\Test`[0-2] 這個帶反引號的資料夾,但是但是

D:\test\Test```[0-2`]\File.txt 不能找到 D:\test\Test`[0-2]\File.txt 的檔案
 (這個問題在 Pwsh7 中已經被修復)

很神奇吧,所謂的雙重引號只有最右邊的斜線有效而已,如果是中間層級的資料夾,雙重反引號是無效的。


好在社群有人發現另一個有趣的現象是 【-include 的邏輯跟 -like 一樣不需要雙重反引號】,我們可以鑽這個空子

function fix_get_item($Path){
    $dir  = (Split-Path $Path) -replace('`','``')
    $file = (Split-Path $Path -Leaf)
    $type = (Get-Item -Path $dir).PSProvider.Name
    (Get-ChildItem $dir -Recurse -Depth 1 -Include $file).FullName
} # fix_get_item 'D:\test\Test```[0-2`]\README`[[0-2]`].md'

利用 Get-ChildItem 去帶的好處是

  1. for迴圈交由 Get-ChildItem 處理絕對比自己正則快很多很多
  2. 不需要去理會邏輯部分,交由現成的函式去處理
  3. 現在父資料跑到最右邊,父資料夾也支持奇耙路徑了 (反引號-任意字符-右方括號)


不過問題淺顯易見,爺資料夾以上還是只能使用正常路徑+通配符,但這問題不大已經足夠覆蓋絕大多數的情況了。


另一個比較易懂得的例子是,下面的方式都可以搜到 `test`[0]`

Get-Item "Z:\Test\test``````[0``]"
Get-Item 'Z:\Test\test``````[0``]'
Get-Item "Z:\Test\*" -Include "test``````[0``]"
Get-Item "Z:\Test\*" -Include 'test```[0`]'
Get-Item "Z:\Test\*" -Include 'test```[0]'  # 最後一個反引號可省略

成功避開雙重反引號的問題,剩下的再繼續消除會導致無法區別了



參考

  1. Wildcard matching when filename has square brackets · PowerShell/PowerShell · Discussion #18146 (github.com)
  2. 可能是修復此問題的關鍵PR: 修復帶有通配符字元的 Get-ChildItem -Path,作者:kwkam ·拉取請求 #9257 ·PowerShell/PowerShell ·GitHub的



2024年5月29日 星期三

Linux 中仿 tree 輸出的 Shell 檔案

Linux 中仿 tree 輸出的 Shell 檔案

2024-05-29 一個比較完整的檔案寫完了放在gist上 Tree.sh (github.com)




某些環境基於某些原因實在是沒辦法,只能自己自幹一個這邊給一個 GPT 的作品,最基本基本的功能是有了


tree.sh

#!/bin/bash

# Function to print the folder name with color
function print_folder() {
    local folder_name=$1
    local COLOR_RESET="\033[0m"
    local COLOR_FOLDER="\033[1;34m" # Blue color for folders
    echo -e "${COLOR_FOLDER}${folder_name}/${COLOR_RESET}"
}

# Function to recursively print the directory tree structure
function print_tree() {
    local directory=$1
    local prefix=$2
    local files=("$directory"/*)
    local last_index=$((${#files[@]} - 1))

    # Check if the directory is empty
    if [ "$files" = "$directory/*" ]; then
        return
    fi

    for i in "${!files[@]}"; do
        local file="${files[$i]}"
        local basename=$(basename "$file")
        local new_prefix="$prefix"

        if [ $i -eq $last_index ]; then
            echo -n "${prefix}└── "
            new_prefix="$prefix    "
        else
            echo -n "${prefix}├── "
            new_prefix="$prefix│   "
        fi

        if [ -d "$file" ]; then
            print_folder "$basename"
            print_tree "$file" "$new_prefix"
        else
            echo "$basename"
        fi
    done
}

# Check if a directory is provided as an argument
if [ -z "$1" ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

# Remove trailing slash from the directory if it exists
directory=$(echo "$1" | sed 's:/*$::')

# Print the initial directory with color
print_folder "$(basename "$directory")"
print_tree "$directory" ""


使用的話跟 tree 差不多沒兩樣先給執行權限

chmod +x tree.sh

然後使用

./tree.sh testdir


輸出結果

testdir/
├── file1.txt
├── file2.txt
├── subdir1/
│   ├── file2.txt
│   ├── file3.txt
│   ├── subsubdir1/
│   │   └── file4.txt
│   └── subsubdir2/
│       └── file5.txt
└── subdir2/
    ├── file3.txt
    ├── file6.txt
    └── subsubdir1/
        └── file7.txt




精簡高壓版本

邏輯一樣的只是完全不管可讀性,能壓能壓版本

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files=("$dir"/*)
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; ([ -d "$file" ] && {
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || echo "$base"); print_tree_core $(($idx + 1))
        }; [ "$files" = "$dir/*" ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; print_tree "$dir" ""
}

貼上之後直接呼叫 tree 就可以用了 (要是原本就有會被覆蓋功能,被蓋了重啟終端就好)

tree testdir



增加顯示有多少個檔案

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files=("$dir"/*)
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; [ -d "$file" ] && { dir_cnt=$((dir_cnt + 1))
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || { echo "$base"; file_cnt=$((file_cnt + 1))
            }; print_tree_core $(($idx + 1))
        }; [ "$files" = "$dir/*" ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; dir_cnt=0; file_cnt=0
    print_tree "$dir"; echo -e "\n$dir_cnt directories, $file_cnt files"
}


增加顯示隱藏檔案

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files
        files=("${dir}"/.* "${dir}"/*); files=(${files[@]/*\*/})
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ "$base" == "." ] || [ "$base" == ".." ]) && {
                print_tree_core $(($idx + 1)); return
            }; ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; [ -d "$file" ] && { dir_cnt=$((dir_cnt + 1))
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || { echo "$base"; file_cnt=$((file_cnt + 1))
            }; print_tree_core $(($idx + 1))
        }; [ ${#files[@]} -eq 0 ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; dir_cnt=0; file_cnt=0
    print_tree "$dir"; echo -e "\n$dir_cnt directories, $file_cnt files"
}





魔法咒語

最後是魔法咒語,特地用函式包起來是要把路徑後置到後面,方便貼上直接些改
_(){ d=$(realpath "$1"); echo "$d"; find "$1" | sort | sed '1d;s|^'"$1"'| |;s/\/\([^/]*\)$/|-- \1/;s/\/[^/|]*/|   /g'; };_ .

為什麼要用醜醜的 |-- 而不是更漂亮的 └── 是因為,用 Ascii 以外的符號很容易就噴亂碼,自己根據情況斟酌使用吧




ASCII 顏色碼 綠色成功 與 紅色失敗

ASCII 顏色碼 綠色成功 Success 與 紅色失敗



偶爾用到每次都要查 gpt 查到煩了寫一份備用下來

# 綠色成功
echo -e "\033[42mSuccess\033[0m"
# 紅色失敗
echo -e "\e[41mFailure\e[0m"



WSL 安裝 ssh 並更改 port 設置自動啟動

WSL 安裝 ssh 並更改 port 設置自動啟動

預設狀態是不會自動啟動的而且 port 可能與主機端有衝突,視情況可以自行改成 2222 口這樣可以避開 Windows 本身如果也有 ssh 的話。

教學用的環境是 Win11 24H3 上安裝的 WSL2 虛擬機運行 Ubuntu 24 版本。版本不同可能會些許的差異。



安裝並配置 OpenSSH

首先,確保 WSL 中已安裝 OpenSSH 伺服器:

sudo apt update
sudo apt install openssh-server


1. 設定 SSH 端口與密碼登入 (選用)

編輯 SSH 設定檔:

sudo nano /etc/ssh/sshd_config

在此可以修改你要的設定如果你不知道要改什麼跳過這個步驟即可。


2. 設定 SSH 伺服器開機自動啟動

設置 systemd 服務管理器:(必須要wsl2才支援)

sudo systemctl enable ssh

必須開著 WSL 視窗才會生效(這服務 systemctl 其實是由 wsl.conf 文件間接啟動的)


如果是舊版的 wsl 1 不支持 systemctl 請這裡編輯 /etc/wsl.conf 文件:

sudo nano /etc/wsl.conf

在文件末尾添加以下內容:

command="service ssh start"

保存文件(按 Ctrl + O),然後退出編輯器(按 Ctrl + X)。


3. 重啟 WSL

在  PowerShell 中執行:

wsl --shutdown

然後再次打開 WSL 後,SSH 伺服器應該會自動啟動並使用設定的端口。


回到 WSL 中執行以下命令確認 SSH 伺服器是否正在運行:
sudo service ssh status


沒有的話手動啟動 (這也表示 步驟2 應該沒設置成功)

sudo service ssh start




從主機端以 SSH 連接 WSL

打開主機端的 PowerShell 嘗試從主機端連接 WSL:
ssh -p 22 your_username@localhost

這裡 -p 的選項預設值就是 22,如果沒有改 port 可以省略不打


至此SSH設置完成可以利用 localhost 由本機登連接了,但還無法給外部電腦連結。

如果沒有要給外部電腦使用,做到這邊就可以了







[番外篇1] 在 WSL 中添加允許的公鑰

WSL 中創建 .ssh 目錄並設置適當的權限:

mkdir -p ~/.ssh && chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys


接著可以手動將你想要使用的公鑰添加到 authorized_keys 文件中:

nano ~/.ssh/authorized_keys

這一步不知道輸入什麼先不用管,確保文件已經被創建可以打開即可

接著按下 Ctrl+X 離開編輯頁面接著繼續做


回到 PowerShell 創建私鑰:

ssh-keygen -t ed25519

預設會詢問是否要保存到使用者底下直接按下 Enter 同意即可
再來會詢問你密碼,空白密碼也是直接 按下 Enter 兩次即可

至此私鑰就創建好了,再來需要把公鑰上傳到WSL中


在 PowerShell 中,執行下面的代碼上傳:

# 獲取本機 publicKey 公鑰
$publicKey = Get-Content -Path "$env:USERPROFILE\.ssh\id_ed25519.pub"

# 使用 SSH 連接到 Linux 伺服器並執行命令
ssh username@localhost "echo ""$publicKey"" >> ~/.ssh/authorized_keys"

接下來會提示要你輸入密碼,輸入WSL密碼即可自動上傳

再來就可以在 PowerShell 中使用簡單的方式登入了

ssh username@localhost




[番外篇2] 從 Windows 轉發 wsl2 的連接埠

在預設情況下 wsl 是掛在另一個張虛擬網卡上的另一個網段裡的,外部電腦是無法直接連接的,要連的話最簡單的辦法是設置虛擬伺服器。

分兩個部分首先在 WSL 中你需要先得到他的ip是多少,在 wsl 中執行

ip addr show eth0

找一下其中 inet 172.25.69.52/20 這個是你的 wsl ip

然後再來是 windows 這個你打開工作管理員,效能頁面中找到乙太網路,這個頁面裡面有寫IP地址


然後添加一下路由表,以管理員模式執行 PowerShell ,然後執行

netsh interface portproxy add v4tov4 `
    listenaddress=192.168.3.13 listenport=22 `
    connectaddress=172.25.69.52 connectport=22


把 192.168.3.13 映射到你的 wsl ip,下面代碼可以確認

netsh interface portproxy show all



然後在執行下面指令打通防火牆

New-NetFirewallRule -DisplayName "WSL2_SSH" -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow

下面代碼可以確認

Get-NetFirewallRule -DisplayName "WSL2_SSH"



至此就可以從外部電腦連接了



[番外篇3] 指定SSH連接使用者與遠端伺服器

在預設情況下當你輸入

ssh 192.168.3.13

預設會使用當前主機使用者名稱連接目標電腦,但如果有設置的話這邊是可以自訂名稱的

例如在以下

notepad $env:USERPROFILE\.ssh\config

文件中輸入

Host MyWSL
  HostName 192.168.3.13
  User chg

就可以使用以下方式登入了

ssh MyWSL




2024-11-12 突然不能連接

已經設置好的伺服器突然連接不上,後來發現重新啟動好像會導致路由失效

下面是刪除路由重建的指令,重啟後需要重新執行一次

netsh interface portproxy delete v4tov4 `
    listenaddress=192.168.3.13 listenport=22

netsh interface portproxy add v4tov4 `
    listenaddress=192.168.3.13 listenport=22 `
    connectaddress=172.25.69.52 connectport=22





參考

2024年5月26日 星期日

Python 如何使用 boxsdk 獲取 Box Accese Token

Python 如何使用 boxsdk 獲取 Box Accese Token

繼上一篇
CHG: Python 如何使用 Json 通過 JWT 請求獲取 Box Accese Token (不使用boxsdk)

詳細介紹了如何手動請求,這裡介紹 boxsdk 的使用方法



安裝 boxsdk[jwt]

參考這裡官方安裝的教學
box/box-python-sdk: Box SDK for Python (github.com)

他有分兩個版本記得後面要括號 jwt 會多裝一些基本套件

pip install "boxsdk[jwt]"

單純的執行就可以安裝好了



方法1 直接直接導入 json 檔案

參考自官方的這篇文章
JWT with SDKs - Box Developer Documentation

以及這裡的教學
BOX API(JWT認証)をPythonで使ったときの備忘録 #Python - Qiita

from boxsdk import JWTAuth
from boxsdk import Client

# JSON 檔案的路徑
CONFIG_FILE = 'config.json'

# 準備使用 BOX API
auth = JWTAuth.from_settings_file(CONFIG_FILE)
client = Client(auth)

# 獲取並顯示用戶資訊(API 測試)
user = client.user().get()
print(f'用戶名稱: {user.name} (ID: {user.id}), 電子郵件: {user.login}')

如此就能輕鬆導入了



方法2 手動導入 JWT物件

這裡是手動從 json 中抽取出數據,並導入 JWT 物件的方法

import json
from boxsdk import Client, JWTAuth

# 加載官方 JSON 檔案
with open('config.json') as json_file:
    config = json.load(json_file)

# 創建 JWTAuth 認證對象
auth = JWTAuth(
    client_id=config['boxAppSettings']['clientID'],
    client_secret=config['boxAppSettings']['clientSecret'],
    enterprise_id=config['enterpriseID'],
    jwt_key_id=config['boxAppSettings']['appAuth']['publicKeyID'],
    rsa_private_key_data=config['boxAppSettings']['appAuth']['privateKey'].replace('\\n', '\n'),
    rsa_private_key_passphrase=config['boxAppSettings']['appAuth']['passphrase']
)

# 獲取訪問令牌
access_token = auth.authenticate_instance()
print(f"訪問令牌: {access_token}")

# 使用訪問令牌創建 Box 客戶端
client = Client(auth)

# 認證並獲取當前用戶資訊
user = client.user().get()
print(f'用戶名稱: {user.name} (ID: {user.id}), 電子郵件: {user.login}')


本質上跟 方法1 跑的是一樣的代碼只是拉出來手動做而已。