欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

macos 上没有 smbclient ,可以用其它命令代替吗 chatgpt 有大用 有大大用

是的,在 macOS 上默认是没有 smbclient 的(它是 Samba 套件的一部分,Linux 常见)。不过你仍然可以用系统自带的命令或者方式来访问 SMB 共享:

可替代方法

1. mount_smbfs

macOS 自带的挂载命令:

mkdir ~/mnt_smb   # 建立挂载点 mount_smbfs //username:password@server/share ~/mnt_smb    



示例:

mount_smbfs //user:123456@192.168.1.100/Public ~/mnt_smb    



注意:如果密码里有特殊符号,需要 URL 编码(比如 @%40)。

卸载:

umount ~/mnt_smb    





2. open (调用 Finder)

直接通过命令行调起 Finder 连接 SMB:

open "smb://username:password@server/share"    



这会在 Finder 里挂载共享盘。


3. osascript (AppleScript)

如果你想脚本化,可以用 AppleScript:

osascript -e 'mount volume "smb://username:password@server/share"'    




4. 如果真的需要 smbclient

  • 可以通过 Homebrew 安装 Samba:

    brew install samba            

                           

    然后你就能用熟悉的 smbclient 命令了。


👉 总结:

  • 如果只是想访问/挂载共享,推荐用 mount_smbfsopen smb://

  • 如果需要像 Linux smbclient 那样交互式操作(比如 ls, get, put),就要装 samba 提供的 smbclient

要不要我帮你写一个 基于 mount_smbfs 的脚本,模拟 smbclient ls/get/put 的功能?



macos 用 mount_smbfs  命令 来代替的 smbclient 的功能

👌 好的,那我们把 smbclient 逻辑改成 mount_smbfs

原理

  • mount_smbfs 会把远程 SMB 共享盘挂载到本地目录(挂载点),之后就可以像普通文件系统一样用 cprsyncmkdir 等命令操作。

  • 相比 smbclient,这种方式更直观,复制文件可以直接用 cp,也能保持目录层级。


示例脚本

#!/bin/zsh REMOTE_HOST="192.168.77.220" REMOTE_SHARE="KKKKK_Audit$" TARGET_DIR="11111111" MOUNT_POINT="/Volumes/KKKKK_Audit" SMBUSER="admin" SMBPASS="123456" # 确保挂载点存在 if [[ ! -d "$MOUNT_POINT" ]]; then    sudo mkdir -p "$MOUNT_POINT" fi # 如果没挂载,先挂载 if ! mount | grep -q "$MOUNT_POINT"; then    echo "正在挂载远程共享..."    mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT" fi # 判断远程目录是否存在,不存在就创建 if [[ ! -d "$MOUNT_POINT/$TARGET_DIR" ]]; then    echo "远程目录 $TARGET_DIR 不存在,正在创建..."    mkdir "$MOUNT_POINT/$TARGET_DIR" else    echo "远程目录 $TARGET_DIR 已存在" fi # 搜索并复制文件 mdfind '((kMDItemFSName == "*BBBB*"cd) || (kMDItemFSName == "*AAAA*"cd) || (kMDItemFSName == "*CCCC*"cd) || (kMDItemFSName == "*DDDD*"cd) || (kMDItemFSName == "*EEEE*"cd)) && ((kMDItemFSName == "*.pdf"cd) || (kMDItemFSName == "*.ppt"cd) || (kMDItemFSName == "*.pptx"cd) || (kMDItemFSName == "*.key"cd) || (kMDItemFSName == "*.png"cd) || (kMDItemFSName == "*.jpg"cd) || (kMDItemFSName == "*.jpeg"cd))' -0 | \ while IFS= read -r -d '' file; do    if [[ "$file" != /Library/* && "$file" != /System/Library/* ]]; then        echo "正在复制: $file"        cp -n "$file" "$MOUNT_POINT/$TARGET_DIR/"    fi done    




脚本说明

  1. mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"
    → 把远程 SMB 共享挂载到 /Volumes/KKKKK_Audit

  2. mkdir "$MOUNT_POINT/$TARGET_DIR"
    → 在远程共享里新建目标目录

  3. cp -n
    → 复制文件,如果目标已存在则不覆盖(防止重复)


⚠️ 注意:

  • mount_smbfs 需要 目标目录为空 或者是个挂载点。

  • 用户密码写在脚本里不太安全,可以考虑放进 Keychain 或者用 ~/.nsmbrc 保存认证信息。

  • 挂载完成后,你也可以手动进入 /Volumes/KKKKK_Audit/11111111 查看文件。


要不要我帮你写一个 自动卸载(umount)共享盘 的版本,这样脚本跑完后不会一直挂着远程共享?

好的 ✅ 我给你加上 自动卸载远程 SMB 共享盘 的逻辑:

改进后的完整脚本

#!/bin/zsh REMOTE_HOST="192.168.77.220" REMOTE_SHARE="KKKKK_Audit$" TARGET_DIR="11111111" MOUNT_POINT="/Volumes/KKKKK_Audit" SMBUSER="admin" SMBPASS="123456" # 确保挂载点目录存在 if [[ ! -d "$MOUNT_POINT" ]]; then    sudo mkdir -p "$MOUNT_POINT" fi # 如果没挂载,先挂载 if ! mount | grep -q "$MOUNT_POINT"; then    echo "正在挂载远程共享..."    mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"    MOUNTED_BY_SCRIPT=1 else    echo "远程共享已挂载"    MOUNTED_BY_SCRIPT=0 fi # 判断远程目录是否存在,不存在就创建 if [[ ! -d "$MOUNT_POINT/$TARGET_DIR" ]]; then    echo "远程目录 $TARGET_DIR 不存在,正在创建..."    mkdir "$MOUNT_POINT/$TARGET_DIR" else    echo "远程目录 $TARGET_DIR 已存在" fi # 搜索并复制文件 mdfind '((kMDItemFSName == "*BBBB*"cd) || (kMDItemFSName == "*AAAA*"cd) || (kMDItemFSName == "*CCCC*"cd) || (kMDItemFSName == "*DDDD*"cd) || (kMDItemFSName == "*EEEE*"cd)) && ((kMDItemFSName == "*.pdf"cd) || (kMDItemFSName == "*.ppt"cd) || (kMDItemFSName == "*.pptx"cd) || (kMDItemFSName == "*.key"cd) || (kMDItemFSName == "*.png"cd) || (kMDItemFSName == "*.jpg"cd) || (kMDItemFSName == "*.jpeg"cd))' -0 | \ while IFS= read -r -d '' file; do    if [[ "$file" != /Library/* && "$file" != /System/Library/* ]]; then        echo "正在复制: $file"        cp -n "$file" "$MOUNT_POINT/$TARGET_DIR/"    fi done # 如果是脚本挂载的,任务完成后自动卸载 if [[ $MOUNTED_BY_SCRIPT -eq 1 ]]; then    echo "任务完成,正在卸载远程共享..."    umount "$MOUNT_POINT" fi    




逻辑改进点

  1. 变量 MOUNTED_BY_SCRIPT

  • 如果是脚本挂载的 → 结束时卸载

  • 如果脚本执行前就已挂载 → 不卸载,避免影响别人

  • umount "$MOUNT_POINT"

    • 在 macOS 里 umount 就能卸载

    • 如果提示 “Resource busy”,可以改用 diskutil unmount force "$MOUNT_POINT"

  • 复制文件时 cp -n

    • 防止覆盖已存在的文件


    要不要我再帮你加一个 日志功能(比如把每次复制的文件路径和时间写到一个 log 文件里 /var/tmp/copy_to_smb.log),方便后面排查?



    # 搜索并复制文件 mdfind '((kMDItemFSName == "*BBBB*"cd) || (kMDItemFSName == "*AAAA*"cd) || (kMDItemFSName == "*CCCC*"cd) || (kMDItemFSName == "*DDDD*"cd) || (kMDItemFSName == "*EEEE*"cd)) && ((kMDItemFSName == "*.pdf"cd) || (kMDItemFSName == "*.ppt"cd) || (kMDItemFSName == "*.pptx"cd) || (kMDItemFSName == "*.key"cd) || (kMDItemFSName == "*.png"cd) || (kMDItemFSName == "*.jpg"cd) || (kMDItemFSName == "*.jpeg"cd))' -0 | \ while IFS= read -r -d '' file; do if [[ "$file" != /Library/* && "$file" != /System/Library/* ]]; then echo "正在复制: $file" cp -n "$file" "$MOUNT_POINT/$TARGET_DIR/" fi done 我要判断 当搜索到文件时 才挂载

    明白了 ✅ 你的需求是:
    👉 只有在 mdfind 真正搜到文件时,才去挂载远程共享盘,否则就不用挂载。

    可以这样写:

    #!/bin/zsh REMOTE_HOST="192.168.77.220" REMOTE_SHARE="KKKKK_Audit$" TARGET_DIR="11111111" MOUNT_POINT="/Volumes/KKKKK_Audit" SMBUSER="admin" SMBPASS="123456" # 先把搜索结果存到数组里 files=() while IFS= read -r -d '' file; do    if [[ "$file" != /Library/* && "$file" != /System/Library/* ]]; then        files+=("$file")    fi done < <(mdfind '((kMDItemFSName == "*BBBB*"cd) || (kMDItemFSName == "*AAAA*"cd) || (kMDItemFSName == "*CCCC*"cd) || (kMDItemFSName == "*DDDD*"cd) || (kMDItemFSName == "*EEEE*"cd)) && ((kMDItemFSName == "*.pdf"cd) || (kMDItemFSName == "*.ppt"cd) || (kMDItemFSName == "*.pptx"cd) || (kMDItemFSName == "*.key"cd) || (kMDItemFSName == "*.png"cd) || (kMDItemFSName == "*.jpg"cd) || (kMDItemFSName == "*.jpeg"cd))' -0) # 如果没有文件,直接退出 if [[ ${#files[@]} -eq 0 ]]; then    echo "未找到匹配的文件,不需要挂载。"    exit 0 fi echo "找到 ${#files[@]} 个文件,准备挂载远程共享..." # 确保挂载点目录存在 if [[ ! -d "$MOUNT_POINT" ]]; then    sudo mkdir -p "$MOUNT_POINT" fi # 如果没挂载,挂载共享 if ! mount | grep -q "$MOUNT_POINT"; then    mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"    MOUNTED_BY_SCRIPT=1 else    MOUNTED_BY_SCRIPT=0 fi # 判断远程目录是否存在,不存在就创建 if [[ ! -d "$MOUNT_POINT/$TARGET_DIR" ]]; then    mkdir "$MOUNT_POINT/$TARGET_DIR" fi # 复制文件 for file in "${files[@]}"; do    echo "正在复制: $file"    cp -n "$file" "$MOUNT_POINT/$TARGET_DIR/" done # 如果是脚本挂载的,复制完后卸载 if [[ $MOUNTED_BY_SCRIPT -eq 1 ]]; then    umount "$MOUNT_POINT" fi                                
                               

    改动重点

    1. 先收集搜索结果 → 用数组 files 存储

    2. 判断数组是否为空[[ ${#files[@]} -eq 0 ]],为空则不挂载,直接退出

    3. 只有找到文件时才挂载远程共享盘


    要不要我再帮你加一个功能:
    比如复制前 判断远程目标目录下是否已经存在同名文件且大小一致 → 如果一致就跳过,不再复制?这样比 cp -n 更智能。



     我想挂载的时候,让用户看不到挂载的盘   隐藏挂载的盘

    明白 ✅。在 macOS 里,mount_smbfs 默认会把 SMB 共享挂载到 /Volumes/共享名,然后用户在 Finder 里会看到一个「磁盘」。如果你想做到「用户看不到挂载的盘」,有几种办法:


    方案 1:挂载到隐藏目录

    你完全可以把挂载点放在一个以 . 开头的目录里(Finder 默认隐藏),比如:

    MOUNT_POINT="/tmp/.smb_mount"    



    然后:

    mkdir -p "$MOUNT_POINT" mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"    



    这样就挂载到 /tmp/.smb_mount,Finder 默认不会显示 /tmp 里的挂载。


    方案 2:挂载到 /Volumes 下的隐藏目录

    如果你还是想挂载到 /Volumes,也可以取个隐藏目录:

    MOUNT_POINT="/Volumes/.KKKKK_Audit"    



    挂载后 Finder 不会显示这个卷。


    方案 3:挂载后设置「不显示」属性

    即使挂载在 /Volumes/KKKKK_Audit,你也可以给挂载点设置 hidden 属性:

    sudo chflags hidden "$MOUNT_POINT"    



    这样 Finder 里就不会显示这个挂载点,但程序依然能访问。


    推荐做法

    最简单、安全的是 方案 1:直接挂载到 /tmp/.smb_mount(或者 /var/tmp/.smb_mount),这样不会出现在 Finder,也不用改系统属性。


    要不要我帮你改一下之前的完整脚本,把挂载点改成「隐藏目录挂载」版本,并自动清理掉挂载点目录?


    #!/bin/zsh #

     REMOTE_HOST="192.168.77.220" 

    REMOTE_SHARE="KKKKK_Audit$"

     MOUNT_POINT="/tmp/.smb_mount" 

    SMBUSER="myusername" 

    SMBPASS="mypassword》}"

     ComputerName=$(scutil --get ComputerName) 

    Serial=$(system_profiler SPHardwareDataType | grep "Serial Number" | awk '{print $4}') 

    Version="$(sw_vers -productName) $(sw_vers -productVersion)" #IP=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | head -n 1 | awk '{print $2}') 

    wired_mac=$(networksetup -listallhardwareports | awk '/Hardware Port: Ethernet/{getline; getline; print $3}'| head -n 1 ) wireless_mac=$(networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/{getline; getline; print $3}'| head -n 1 ) curr_user=$(id -un) 

    TARGET_DIR="${curr_user}" 

    # 先把搜索结果存到数组里 

    files=() while IFS= read -r -d '' file; 

    do 

    if [[ "$file" != /Library/* && "$file" != /System/Library/* ]]; then files+=("$file") fi done < <(mdfind '((kMDItemFSName == "*BBBB*"cd) || (kMDItemFSName == "*AAAA*"cd) || (kMDItemFSName == "*CCCC*"cd) || (kMDItemFSName == "*DDDD*"cd) || (kMDItemFSName == "*EEEE*"cd)) && ((kMDItemFSName == "*.pdf"cd) || (kMDItemFSName == "*.ppt"cd) || (kMDItemFSName == "*.pptx"cd) || (kMDItemFSName == "*.key"cd) || (kMDItemFSName == "*.png"cd) || (kMDItemFSName == "*.jpg"cd) || (kMDItemFSName == "*.jpeg"cd))' -0) if [[ ${#files[@]} -eq 0 ]];then (/usr/bin/osascript -e "display dialog \"没有关键字文件\" buttons {\"OK\"} default button \"OK\" with title \"敏感文件\" ") 

    exit 0 

    fi 

    # 确保挂载点目录存在 

    if [[ ! -d "$MOUNT_POINT" ]]; then sudo mkdir -p "$MOUNT_POINT" fi # 如果没挂载,挂载共享 if ! mount | grep -q "$MOUNT_POINT"; then mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT" fi 

    # 判断远程目录是否存在,不存在就创建

     if [[ ! -d "$MOUNT_POINT/$TARGET_DIR" ]]; then 

        mkdir "$MOUNT_POINT/$TARGET_DIR" 

    fi 

    # 复制文件 

    for file in "${files[@]}"; do 

       echo "正在复制: $file" \cp -f "$file" "$MOUNT_POINT/$TARGET_DIR/" 

    done 

    #卸载 

    umount "$MOUNT_POINT" 

    exit 0

     mount_smbfs: URL parsing failed, please correct the URL and try again: Invalid argument mkdir: /tmp/.smb_mount/mmmmm: Permission denied 正在复制: /Users/mmmmm/CCCC.pdf cp: directory /tmp/.smb_mount/mmmmm does not exist 正在复制: /Users/mmmmm/AAAA.pptx cp: directory /tmp/.smb_mount/mmmmm does not exist 正在复制: /Users/mmmmm/AAAA.ppt cp: directory /tmp/.smb_mount/mmmmm does not exist umount: /tmp/.smb_mount: not currently mounted


    你这个脚本报错主要有两个点:


    1. mount_smbfs: URL parsing failed

    报错说明 SMB 路径写得不对。
    在 macOS 里,mount_smbfs 的 URL 必须符合下面格式:

    mount_smbfs //[domain;][user[:password]@]server/share mount_point    





    你的写法:

    mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"    




    问题出在 REMOTE_SHARE="KKKKK_Audit$" —— $ 在 zsh 里会被当成变量展开。
    所以实际上传给 mount_smbfs 的路径不正确。

    ✅ 解决办法 → 对共享名里的 $ 进行转义

    REMOTE_SHARE='KKKKK_Audit\$'    





    2. mkdir: /tmp/.smb_mount/mmmmm: Permission denied

    说明 SMB 挂载没有成功,所以 /tmp/.smb_mount 还是本地目录,不是远程目录。
    因此你在上面 mkdir "$MOUNT_POINT/$TARGET_DIR" 的时候,其实是在本地 /tmp/.smb_mount 下建目录(而且你用了 sudo mkdir -p "$MOUNT_POINT" 创建,root 拥有它 → 普通用户就没权限写入)。

    ✅ 解决办法 →

    • mount_smbfs 成功挂载后,$MOUNT_POINT 就会变成远程共享目录,你才能写入。

    • 不要用 sudo mkdir -p "$MOUNT_POINT",直接用普通用户创建就行。


    修正后的脚本关键部分

    REMOTE_HOST="192.168.77.220" REMOTE_SHARE="KKKKK_Audit\$"   # 注意这里的转义 MOUNT_POINT="/tmp/.smb_mount" SMBUSER="myusername" SMBPASS="mypassword>}" # 确保挂载点目录存在(用普通用户权限即可) [ ! -d "$MOUNT_POINT" ] && mkdir -p "$MOUNT_POINT" # 如果没挂载,挂载共享 if ! mount | grep -q "$MOUNT_POINT"; then    echo "正在挂载远程共享..."    mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT" fi    




    3. 建议加的健壮性

    挂载后再检查是否真的成功:

    if ! mount | grep -q "$MOUNT_POINT"; then    echo "挂载失败,请检查账号/密码/共享名"    exit 1 fi    



    这样能避免后续 mkdircp 报一堆错。


    要不要我帮你把整个脚本整理一下,改成 自动判断挂载是否成功 → 再复制文件 → 最后卸载 的稳定版?


    # mount_smbfs "//myusername:mypassword}>@$192.168.77.220/KKKKK_Audit\$" "/tmp/.smb_mount"

    mount_smbfs: URL parsing failed, please correct the URL and try again: Invalid argument

    嗯,这个报错是因为 密码里有特殊字符>}$ 等)和 共享名里的 $,在 mount_smbfs 的 URL 里没被正确转义。

    macOS 的 mount_smbfs 是非常挑剔的,它要求 URL 必须符合:

    mount_smbfs //[domain;][user[:password]@]server/share mount_point    


    解决办法有三种:


    ✅ 方法 1:用引号和百分号转义特殊字符

    mount_smbfs 遵循 URL 编码规则,例如:

    • >%3E

    • }%7D

    • $%24

    • {%7B

    所以你的命令应该改成:

    mount_smbfs "//myusername:url_encode_mypassword@192.168.77.220/KKKKK_Audit%24" "/tmp/.smb_mount"    





    ✅ 方法 2:避免在命令里写密码

    推荐使用 凭证文件~/.nsmbrc(安全很多):

    1. 编辑 ~/.nsmbrc

      [SERVER:192.168.77.220] password=mypassword}> username=myusername            

             

                   

    2. 权限保护

      chmod 600 ~/.nsmbrc            

             

         

    3. 挂载时就不用写密码了:

      mount_smbfs "//myusername@192.168.77.220/KKKKK_Audit$" "/tmp/.smb_mount"            

             

         


             


    ✅ 方法 3:让脚本运行时交互输入密码

    read -s -p "SMB 密码: " SMBPASS echo mount_smbfs "//$SMBUSER:$SMBPASS@$REMOTE_HOST/$REMOTE_SHARE" "$MOUNT_POINT"    



    建议

    如果是长期要用的自动化脚本 → 用 方法 2 (~/.nsmbrc) 最稳妥;
    如果只是测试 → 用 方法 1 URL 编码 就能解决你现在的报错。


    要不要我帮你把你的整个脚本改成 方法 2(用 ~/.nsmbrc 免密码) 的安全版本?


    username password 分别为 username="aaaa@" password="bbbbb@" 如何进行url 编码 mount_smbfs //$username:$password@server/share ~/mnt_smb


    你这种情况需要把 用户名和密码单独进行 URL 编码,否则像 @:/ 这种特殊字符会让 mount_smbfs 解析错误。

    例如:

    • 原始:

      username="aaaa@" password="bbbbb@"


    • URL 编码后:

      username="aaaa%40" password="bbbbb%40"


      • @%40
        所以:

    然后命令改成:

    mount_smbfs //${username}:${password}@server/share ~/mnt_smb


    👉 结果就是:

    mount_smbfs //aaaa%40:bbbbb%40@server/share ~/mnt_smb



    ⚡ 小技巧:在 macOS 里你可以用 python3 -cphp -r 来做快速 URL 编码:

    python3 -c 'import urllib.parse; print(urllib.parse.quote("bbbbb@"))'


    会输出:

    bbbbb%40


    要不要我帮你写一个 shell 脚本函数,输入原始账号密码自动转成 URL 编码,然后直接执行 mount_smbfs


    好👌 我帮你写一个 Shell 脚本函数,你只需要输入用户名、密码、服务器和共享目录,它会自动做 URL 编码并挂载:

    #!/bin/bash # 函数:URL 编码 urlencode() {    python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$1" } # 挂载函数 mount_smb() {    local username password server share mnt    username=$(urlencode "$1")    password=$(urlencode "$2")    server="$3"    share="$4"    mnt="$5"    mount_smbfs "//$username:$password@$server/$share" "$mnt" } # 使用方法示例: # mount_smb "aaaa@" "bbbbb@" "server" "share" "$HOME/mnt_smb"



    📌 使用步骤

    1. 把上面代码保存为 mount_smb.sh

      nano ~/mount_smb.sh


      粘贴内容并保存。

    2. 给脚本执行权限:

      chmod +x ~/mount_smb.sh


    3. 挂载 SMB:

      ~/mount_smb.sh "aaaa@" "bbbbb@" "server" "share" "$HOME/mnt_smb"


    这样就不用自己管 URL 编码了,用户名和密码里即使有 @, :, #, / 也能正常处理。


    要不要我帮你把这个函数直接写进 .zshrc(或 .bashrc),这样你以后在终端里随时直接用 mount_smb

    shell 没有url编码这个函数吗

    对的 👍,在 原生 POSIX shell / bash / zsh 里面是没有内置的 urlencode 函数的,需要自己实现。

    你有几种选择:


    ✅ 方案 1:用 python3

    (推荐,简单可靠,Mac 默认就有 python3)

    python3 -c 'import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))' 'aaaa@'

    输出:

    aaaa%40

    ✅ 方案 2:用 perl

    perl -MURI::Escape -e 'print uri_escape("aaaa@")'

    输出:

    aaaa%40

    ✅ 方案 3:用 jq

    如果系统装了 jq(常见 JSON 工具),也能干这个:

    jq -rn --arg v "aaaa@" '$v|@uri'

    输出:

    aaaa%40

    ✅ 方案 4:自己写一个 shell 函数

    如果你不想依赖 Python/Perl/JQ,可以写一个纯 bash/zsh 函数来做 URL 编码:

    urlencode() {  local length="${#1}"  for (( i = 0; i < length; i++ )); do    local c="${1:i:1}"    case $c in      [a-zA-Z0-9.~_-]) printf '%s' "$c" ;;      *) printf '%%%02X' "'$c"    esac  done }

    用法:

    urlencode "aaaa@" # 输出 aaaa%40

    ⚡ 总结:

    • macOS 默认没有 urlencode 内置函数

    • 一般用 python3perl 最方便

    • 想通用就自己写个 urlencode() 函数放到 .zshrc.bashrc

    要不要我帮你写一个 最终简化版 mount_smb 函数(带内置 urlencode,不依赖 python/perl),直接加到 .zshrc 用?


    您说:
    ChatGPT 说:

    好,那我给你写一个 最终简化版的 mount_smb 函数,它里面自带 urlencode,不依赖 python/perl/jq,直接粘贴进你的 ~/.zshrc~/.bashrc 就能用。


    函数代码

    # URL 编码函数(纯 Shell 实现) urlencode() {  local length="${#1}"  for (( i = 0; i < length; i++ )); do    local c="${1:i:1}"    case $c in      [a-zA-Z0-9.~_-]) printf '%s' "$c" ;;   # 安全字符原样输出      *) printf '%%%02X' "'$c" ;;            # 其他字符转 %XX    esac  done } # SMB 挂载函数 mount_smb() {  if [ $# -lt 5 ]; then    echo "用法: mount_smb <username> <password> <server> <share> <mountpoint>"    return 1  fi  local username password server share mnt  username=$(urlencode "$1")  password=$(urlencode "$2")  server="$3"  share="$4"  mnt="$5"  echo "正在挂载 //$username:*****@$server/$share$mnt ..."  mount_smbfs "//$username:$password@$server/$share" "$mnt" }

    使用方法

    1. 编辑 .zshrc(如果你用 bash,就改 .bashrc):

      nano ~/.zshrc


      粘贴上面代码,保存。

    2. 让配置生效:

      source ~/.zshrc


    3. 使用命令挂载:

      mount_smb "aaaa@" "bbbbb@" "server" "share" "$HOME/mnt_smb"


    👉 其中 aaaa@bbbbb@ 会自动被转成 aaaa%40bbbbb%40,不需要你手动编码。


    要不要我顺便帮你加一个 自动创建挂载目录(如果不存在) 的功能?这样就不用每次手动 mkdir ~/mnt_smb 了。



    # perl -MURI::Escape -e 'print uri_escape("aaaa")'

    aaaa%                                                                                    

    macos perl url 编码 结果 多一个% 


    image.png

    在 macOS 上运行 Perl 命令进行 URL 编码时,末尾多出的 % 实际上是终端提示符的一部分,而不是 uri_escape 的输出结果。

    问题分析:

    你的命令:

    bash
    perl -MURI::Escape -e 'print uri_escape("aaaa")'

    输出:

    aaaa%
    

    这里的 % 并不是来自 uri_escape 函数,而是:

    1. uri_escape("aaaa") 正确地输出了 "aaaa"(因为这个字符串不需要编码)

    2. 由于你没有在命令末尾添加换行符 \n,终端的提示符 % 直接紧跟在输出后面

    解决方案:

    要解决这个问题,可以在 Perl 命令中添加换行符:

    bash
    perl -MURI::Escape -e 'print uri_escape("aaaa"), "\n"'

    输出将会是:

    aaaa
    

    如果你的确需要 URL 编码:

    比如编码特殊字符:

    bash
    perl -MURI::Escape -e 'print uri_escape("a a"), "\n"'

    输出:

    a%20a
    

    总结:

    多出的 % 是终端行为,不是 Perl 或 URI::Escape 的问题。始终在打印内容后添加换行符是一个好习惯。


    普通分类: