2024年6月11日 星期二

python click 參數中自訂自己的檢查類 type

python click 參數中自訂自己的檢查類 type

原生函式其實已經非常足夠了,只是如果專案比較刁鑽一點又不想把檢查函式寫在 command 函式中,而是傳入之前檢查可以自訂類來做

目標先挑一個你覺得有用的免得要重寫太多功能,這邊假設是路徑就挑 click.Path 這個類別繼承,然後在此之上加上自己的功能

# 自訂 type 類別 ExtPath 用例
class ExtPath(click.Path):
    def __init__(self, my_check=False, **kwargs):
        self.my_check = my_check
        super().__init__(**kwargs)

    def convert(self, value, param, ctx):
        print(f"ExtPath.convert():: 啟動此函數的對象參數是 {param.name} 目前值為 {value}, 自訂參數 my_check={self.my_check}")
        return super().convert(value, param, ctx)

# 群組命令3:: demo2
@entry.command()
@click.argument('name', required=True, nargs=1, type=ExtPath())
@click.argument('path', required=True, nargs=-1, type=ExtPath(my_check=True, exists=True))
def demo2(name, path):
    cli.echo_inf_message(f"[\n    name: {name}\n    path: {path}\n]")
    """獲取從cli設置的初始化信息"""

大致上會用到的部分都寫上了,自己參考著應該就能知道怎麼使用了


範例用例

(.venv) PS C:\Users\user\OneDrive\Git Repository\PyCli_Template> myapp demo2 Test setup.py *.ps1     
ExtPath.convert():: 啟動此函數的對象參數是 name 目前值為 Test, 自訂參數 my_check=False
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 setup.py, 自訂參數 my_check=True
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 deploy.ps1, 自訂參數 my_check=True
[
    name: Test
    path: ('setup.py', 'deploy.ps1')
]


需要講的大概就是順序了,這個類的順序在參數擴展之後的 for 迴圈之內,就算是附加在 nargs=-1 裡面也會自動跑多次,可以從範例中看到。

這裡處理完畢之後就會正式進入 comannd 函式了,可以在這裡修改變數值但不是很建議,可能會跟官方功能有衝突。





2024年6月7日 星期五

python 裝飾器 時間計時範例

python 裝飾器 時間計時範例

來自高天的講解中複製出來的代碼
【python】装饰器超详细教学,用尽毕生所学给你解释清楚,以后再也不迷茫了!

import time

def timeit(iteration):

    def inner(f):

        def wrapper(*args, **kwargs):
            start = time.time()
            for _ in range(iteration):
                ret = f(*args, **kwargs)
            print(time.time() - start)
            return ret

        return wrapper

    return inner

@timeit(1000)
def double(x):
    return x * 2

double(2)

這個例子很關鍵的地方在於事後忘記了看一眼就馬上明白了

怎麼理解最快,這個最快的辦法就是不要理解

首先第一個問號區 *args, **kwargs ,只需要記住一件事情這整個區塊叫做 完美轉發 我不管前面的人給我什麼反正我就是直接複製對方的參數設定值

剩下的直接都當作一個區塊記下來就好了,只需要知道從 1000 那個位置可以設置 for 迴圈的變數,然後在 ret 的位置就是執行原本的函式

在 wrapper 中你可以對這個函式的前跟後動手,你希望在函式執行前先弄點什麼就寫在 ret 之前,你希望在執行後再弄點什麼就寫在 ret 之後

而這個對函式前後動手的行為就是裝飾器中的一個核心價值了,只需要透過追加一行就可以包裝函式前後的動作了




如果要放在類裡面參考這篇

【python】如何在class内部定义一个装饰器?这里的坑你要么不知道,要么不会填! (youtube.com)


2024年6月6日 星期四

python click 如何避免參數中的通配符被展開

python click 如何避免參數中的通配符被展開

在這篇討論中有被提到
Expand globs in arguments on Windows? · Issue #1096 · pallets/click (github.com)

click預設會通過 glob 展開通配符這是為了確保在 Windows 或 Linux 上能獲得一致的效果
不過反過來說如果你就是要傳入通配符,其實沒有任何手段可以避免的,原作似乎就沒開放了

討論的最後有提到改進在這裡
expand patterns in Windows args by davidism · Pull Request #1830 · pallets/click (github.com)

那就沒辦法只能硬幹了,就把這代碼幹掉

import click

# 定義一個新的 _expand_args 函數,直接返回原始參數
def _no_expand_args(args):
    return args
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args

@click.command()
@click.argument('path', type=str)
def process_path(path):
    click.echo(f"Received path: {path}")

if __name__ == '__main__':
    process_path()


這樣一來就不會被騷擾了,反過來說你也可以在這裡寫上屬於自己的條件

上面是簡寫完整一點的整個函式其實是長這樣的

# 定義一個新的 _expand_args 函數,直接返回原始參數
import typing as t
def _no_expand_args(
    args: t.Iterable[str],
    *,
    user: bool = True,
    env: bool = True,
    glob_recursive: bool = True,
) -> t.List[str]:
    return list(args)
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args


原始代碼可以在這裡看,可以參考是如何用 glob 展開變數的
click/src/click/utils.py at 923d197b56caa9ffea21edeef5baf1816585b099 · pallets/click (github.com)



補充說明一下流程,我覺得 click 原作這個解不夠漂亮,但確實已經是沒有辦法中的一個還算可以的辦法了。

原作的做法是收到來自命令的參數時,就先進行展開,不管是 option 還是 augument 都會被展開,這個有個問題是

在 linux 上 bash 會直接展開後才輸入,所以實際 click 拿到的參數已經是展開後的,看起來很美好的統一了 Win 和 linux,其實背後是犧牲了一個特性

如果想試圖傳遞通配符給程序,通常的做法是在 bash 中輸入雙引號,以便告訴 bash 不要展開這個字串直接輸入到程序中

問題就在這裡,click拿到參數之後不管如何總之每個都先過一輪 glob() 就對了,所以最終還是會被展開了,儘管已經用雙引號

好處就是統一了 Windwos 跟 Linux 的邏輯,背後是犧牲透過雙引號輸入通配符號的可能性, Click 強制 glob() 導致無論如何參數都會被展開




不過其實有邏輯漏洞可以卡,透過這個方式 `myapp -p"*.py"` 或是全寫也行,這樣過 glob() 的時時候由於 `-p"*.py"` 不是路徑就不會被展開了

如果 -p 後面有空格,那就會被切開來分兩次進glob() 還是會被展開,類似技巧還有別的組合可以實現


繼續回來原本的話題,這個要比較合理的解決,估計還是只能開放 glob 的自由度吧,預設不要開,自己決定要不要展開,而且最好還是可以決定系統層級的 `glob_win=Ture`, `glob_unix=Ture`。

這樣至少 linux 上還可以保持透過雙引號控制要不要展開,而Windwos這沒辦法了終端機本來就不會展開了,不管雙不雙引號都一樣。

兩邊系統的統一性我覺得不是那個重要,對於 win 本來就要知道系統不會幫你展開,對於 unix 本來就要知道系統會擅自展開。

函式只需要提供一個快捷讓你自己決定要不要賭這個洞。





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

PowerShell 通配符 Get-Item 無法帶有方括號名稱的檔案

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

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

Get-Item [1]File.txt

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

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


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

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



情況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('`','``'))


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




深挖bug的本質

2024-10-09

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

  1. 反引號轉義不一致 ·問題 #7999 ·PowerShell/PowerShell (github.com)
  2. 工作目錄路徑與相對路徑中的通配符 ·問題 #24260 ·PowerShell/PowerShell (github.com)


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

永遠都無法區分路徑字串上的 D:\test\Test`[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 的檔案

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


好在社群有人發現另一個有趣的現象是,-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. 現在父資料跑到最右邊,父資料夾也支持奇耙路徑了 (反引號-任意字符-右方括號)


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





參考

  1. Wildcard matching when filename has square brackets · PowerShell/PowerShell · Discussion #18146 (github.com)



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 服務管理器:

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)。


4. 重啟 WSL

在  PowerShell 中執行:

wsl --shutdown

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


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



從主機端以 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





參考