65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Windows 上正确进行 Bash 脚本编写:将 SH 脚本关联到 WSL (Windows 10)(启用拖放 .sh 文件)(+其他 Linux 可执行文件)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2017 年 11 月 9 日

CPOL

5分钟阅读

viewsIcon

27210

downloadIcon

116

在 Windows 10 上正确设置 Shell 脚本的指南。

引言

终于发布了!经过漫长的 Beta 测试阶段,适用于 Linux 的 Windows 子系统(以前称为 Windows 上的 Ubuntu Bash)终于在 Windows 10 版本 1709 中达到了其第一个稳定版本,它不会让您失望:随着 Microsoft Store 的集成和新发行版的逐步发布,WSL 似乎正在发挥其真正潜力,因为 bug 得到修复,越来越多的用户开始倾向于它而不是多启动系统(但让我们避免引发 Windows 与 Linux 操作系统的战争,这无关本文内容 😉)。

正如您可能已经知道的(除非您偶然看到这篇文章),WSL - 默认情况下 - 仍然不允许您将 SH 脚本(*.sh 文件)关联到 *bash.exe* 或 *wsl.exe*(或任何其他 Linux 可执行文件,如 *.out 和 *.elf),也不允许将文件和文件夹拖放到脚本上来将它们作为参数传递。此外,如果您是刚开始使用 Linux 的 Windows 用户,在创建/编辑这些脚本时可能会遇到困难。

但是不用担心,因为解决方案(针对前两个问题)很简单:一个 Windows 注册表项!

SH/ELF 文件关联和拖放

如果您是 Windows 新手,想知道注册表项(或 Windows 注册表)到底是什么,并且您不是一个懒惰的读者,您可以通过点击这里找到所有问题的答案,我只想告诉您,您可以通过简单地双击来安装(或根据专业术语使用“合并”)一个注册表项;在我看来,这非常方便。

所以,如果您只是来找修复程序的,下载就在文章顶部:请记住下载最新版本;除非您喜欢 bug 和缺失的功能(我有什么资格评判您)。合并注册表项后,只需将 *bash.exe* 或 *wsl.exe* 设置为打开 *.sh* 文件或其他 Linux 可执行文件(如 *.elf)的默认程序即可。

该脚本还支持以下特定发行版的执行文件:*ubuntu.exe*、*SLES-12.exe*、*openSUSE-42.exe*、*kali.exe* 和 *debian.exe*。

注册表项还将启用将文件拖放到脚本/可执行文件上来将其用作参数的功能,提供一个右键单击 > 以管理员身份运行选项,包含一个可选的右键单击 > 使用 Vim 编辑器编辑的项,以及一个额外的右键单击到文件夹 > 在此处打开 WSL 的项。

请注意,您现在也可以将任何类型的 Linux 可执行文件关联到 WSL:这包括在编译代码后创建的 *.out* 文件、ELF 可执行文件和其他可执行二进制文件。由于 Linux 操作系统不像 Windows 那样需要为这些文件使用扩展名,您可以给它们任何扩展名,一个尚未使用过的文件扩展名(我建议使用 *.lxx*)或者直接使用 *.elf* 都可以。

创建和编辑 .sh 文件

您可以认为这是简单的一步,但您错了……至少直到您安装了一个好的文本编辑软件。是的,众所周知,Windows 的记事本就是来自地狱的产物,您绝对不应该在任何情况下使用它。

开个玩笑,Windows 记事本只设计用于处理 Windows 行尾符(“`\r\n`”)并且不会识别 Unix 行尾符(“`\n`”),但是有很多替代方案,例如 Notepad++,或者我个人的最爱,Programmer's Notepad。

或者,上面可下载的 rar 压缩包将提供一个可选的注册表项,用于在上下文菜单(文件右键单击)中添加编辑(Linux Vim 编辑器)选项。Vim 不是最实用的编辑器,但它能完成工作(在某些发行版上可能存在 bug)。

安装后,只需创建一个 *new.txt* 文件,将其扩展名重命名为 *.sh*,用[非记事本]打开它,找到文件属性/行尾符(例如:在 Programmer's Notepad 中为“文件”>“属性”)并将其行尾符更改为 Unix 格式(如果您使用 Vim 则无需担心);您只需要为新文件执行此操作。

SH 关联脚本的工作原理

我们将使用 `[HKEY_CLASSES_ROOT\Applications\bash.exe]` 和 `[HKEY_CLASSES_ROOT\Applications\wsl.exe]` 中的注册表项。

首先,通过在“*...\shellex\DropHandler*”项中添加 `DropHandler` 来启用所有用 *bash.exe* 或 *wsl.exe* 打开的文件的拖放功能;我不会去创建一个新的拖放处理程序,所以我将使用 vbs 文件的拖放处理程序。

{60254CA5-953B-11CF-8C96-00AA00B8708C}

Windows 注册表中的“*...\shell\open\command*”项可以——您猜对了——执行一个命令;对于打开方式命令,在大多数情况下是这样的。

"PROGRAM_TO_OPEN_THE_FILE_WITH'S_PATH\program.exe" "%L" "%*"

每次打开与 *program.exe* 关联的文件时,该命令都会运行;`%L` 是我们正在打开的文件的路径,而 `%*` 是我们通过拖放处理程序获得的被拖放文件的路径;这对于 WSL **不起作用**。

然而,我们可以利用这个系统:*bash.exe* 和 *wsl.exe* 都可以被调用来静默执行一个 bash 脚本:前者使用 `-c` 选项,后者则直接写命令。因此,我们**可以**做到的是,编写一个小的 bash 脚本,将所有参数从 Windows 路径转换为 Unix 路径,然后运行实际的 *.sh* 脚本 / Linux 可执行文件。

#Gets all file paths without expansion/substitution
read -r -d '' path_param <<'EOF'
PARAMETERS_PATHS_HERE
EOF
read -r -d '' path_exec <<'EOF'
SCRIPT_PATH_HERE
EOF

#Parses all dragged files' paths from Windows paths to unix paths
path_param=$(echo $path_param | tr -d '"' | sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | 
                                sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\');
mapfile -t path_param <<< "$path_param";
path_param=("${path_param[@]//:}");

#Same, but with the .sh script path
path_exec=$(echo $path_exec | sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | 
                              sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\'); 
path_exec="${path_exec//:}";

#Sets working directory to the folder where the script is located
cd "${path_exec%\/*}";

#Executes script with or without parameters
if [[ ${path_param[@]} == "" ]];
    then "$path_exec";
    else "$path_exec" "${path_param[@]/#${path_exec%\/*}\/}";
fi;

#Leaves WSL console open after the .sh script finishes executing (optional)
cd ~; bash;

为了将所有这些代码塞进一个 Windows 注册表项中,我们必须将所有 `%` 字符替换为 `%%`,并将所有换行符替换为 `;`。例外是 heredoc 命令(初始读取命令)之间的换行符,我们将使用一个换行符,为了方便,我将用便便表情符号来表示它(💩)。

因此,让我们将您在上面看到的(相对)漂亮的​​代码转换为以下形式的 *bash.exe* 和 *wsl.exe* 命令项(`REG_EXPAND_SZ` 类型)的“丑陋”代码。

"%SYSTEMROOT%\System32\bash.exe" -c printf\ \"\"💩read -r -d '' path_param 
<<'EOF'💩%*💩EOF💩read -r -d '' path_exec <<'EOF'💩%L💩EOF💩path_param=$
(echo $path_param | tr -d '"' | sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | 
sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\'); mapfile -t path_param <<< "$path_param"; 
path_param=("${path_param[@]//:}"); path_exec=$(echo $path_exec | 
sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\'); 
path_exec="${path_exec//:}"; cd "${path_exec%%\/*}"; if [[ ${path_param[@]} == "" ]]; 
then "$path_exec"; else "$path_exec" "${path_param[@]/#${path_exec%%\/*}\/}"; fi; cd ~; bash;
"%SYSTEMROOT%\System32\wsl.exe" read -r -d '' path_param <<'EOF'💩%*💩EOF💩read -r -d '' 
path_exec <<'EOF'💩%L💩EOF💩path_param=$(echo $path_param | tr -d '"' | 
sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\'); 
mapfile -t path_param <<< "$path_param"; path_param=("${path_param[@]//:}"); 
path_exec=$(echo $path_exec | sed 's/[[:space:]]\([A-Z]:\)/\n\1/g' | 
sed 's/[A-Z]:/\/mnt\/\L&/g' | tr '\\' '\/'\'); path_exec="${path_exec//:}"; 
cd "${path_exec%%\/*}"; if [[ ${path_param[@]} == "" ]]; then sudo "$path_exec"; 
else sudo "$path_exec" "${path_param[@]/#${path_exec%%\/*}\/}"; fi; cd ~; bash;

欢迎来到编程的黑暗面。

历史

  • [2017/11/08] CodeProject 首次上传(脚本版本 8)
  • [2017/11/15] 极大改进了解析器脚本,切换到 VBS DropHandler(脚本版本 9)
  • [2017/11/16] 添加了特定发行版可执行文件支持,修复了“在此处打开”的 bug(脚本版本 10)
  • [2018/06/25] 支持新发行版并修复了与图标相关的 bug(脚本版本 11)
  • [2018/06/28] 修复了小的“使用 Vim 编辑” bug(脚本版本 12)
© . All rights reserved.