作者falcon (falken)
看板Windows
标题[问题] 安装 Console 应用程式
时间Thu Apr 25 19:36:43 2024
准确来说是把程式目录新增至环境变数 PATH 之中,
方便在终端机中使用档名或 basename 呼叫程式并执行。
但当程式越来越多後 PATH 就会变得越来越臃肿。
为了保持 PATH 简洁,需要统一使用同一个目录,我尝试了以下方法。
方法 1. 在同一目录下放置所有程式的子目录、档案
这是最简单粗暴的方法
但,由於所有程式的子目录与档案都混在一起,
可能遇到档案冲突,且管理上也很不方便。
方法 2. 在同一目录建立目标程式的符号连结
PowerShell:
--------
New-Item -ItemType SymbolicLink -Path program1.exe -Target "C:\My Programs\pro
gram1\program1.exe"
--------
符号连结的好处是简单且方便管理。
但通过符号连结执行程式会因为找不到依赖的档案 (例如 dll 档) 而无法正常执行,
所以只适合用於单 exe 档的程式。
或者,要为所有项目也建立符号连结。
但这可能会与其他程式的同名子目录或档案的符号连结冲突。
方法 3. 在同一目录下建立执行目标程式的 bat 档案
program1.bat:
--------
@echo off
"C:\My Programs\program1\program1.exe" %*
exit /b %ERRORLEVEL%
--------
bat 档的好处是可以使用 set 命令预先设定必要的临时环境参数。
但使用 bat 档会遇到一个棘手的问题。
由於 bat 的 args 需要按照 cmd 的方式跳脱字元,
这导致我必须重写现有 powershell 脚本里的受影响的程式的执行命令。
或者必须在 bat 内就跳脱所有特殊字元,
但 cmd 贫弱的字串处理能力,还有一堆例外状况,想到就让人怯步…
以上三种方法无论是哪一种都有缺点
搞了半天还是乖乖往 PATH 内新增每个程式的目录比较实际...
有没有人知道更加漂亮的解决方式?
--
Sent from
PTTopia
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 118.231.152.70 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Windows/M.1714045005.A.193.html
1F:推 smallreader: PATH为什麽会嫌太杂乱?太长再新增一行就好了吧04/25 19:51
2F:→ smallreader: 啊 第二个方法 改用python就不怕跳脱字元了04/25 20:00
3F:→ smallreader: subprocess.run(["路径"]+sys.argv[1:])04/25 20:18
python 脚本的问题就是在 powershell 或 cmd 无法只使用 basename 呼叫并执行
PS> .\program # 执行名为 program 的可执行档,例如 program.bat
PS> python .\program.py # 执行 python 脚本 program.py
这会导致我要需要重写我所有的 powershell 脚本
4F:推 tonyhsie: 正常都是1搭配3吧 大部分单一执行档集中在一个目录04/26 14:06
5F:→ tonyhsie: 少部分一堆档案的程式 改用批次档 放在跟1同一目录下04/26 14:07
微软自己的 winget 安装 Console 程式就是在同一资料夹为所有 exe 档建立符号连结
遗失依赖档案的问题在 GitHub 上也是个开启中的 Issue
6F:→ tonyhsie: 你如果会写C#的话 可以不要用bat 自己写个简单的壳就好04/26 14:10
7F:→ tonyhsie: 壳作的事就是单纯切目录跟传递参数而已04/26 14:11
8F:→ tonyhsie: 然後把壳集中放在同一目录下就好04/26 14:11
这个方法稍微尝试一下,目前还没发现什麽问题
9F:推 giacch: 若是Windows, 就是安装程式, 建立"捷径" 04/26 14:18
10F:推 giacch: 纯Console环境, 不是管理PATH, 就是要弄出类似"捷径"的BAT04/26 14:21
11F:推 giacch: PATH新增C:\BIN, BIN底下建立各程式的BAT(当捷径用)04/26 15:58
12F:→ giacch: BAT 内容包含 CD PathToProgram 与 Program.exe04/26 15:59
13F:推 giacch: 更正 Program.exe %* 不要"号04/26 16:01
问题不於如何执行目标程式,而是如何跳脱 args 中的所有特殊字元
例如在 powershell 用 ffmpeg 转换影片格式
原始 ffmpeg.exe
PS> .\ffmepg -i input.mp4 ^outpu.mkv # 实际输出档案名称为 "^outpu.mkv"
ffmpeg.bat
PS> .\ffmpeg -i input.mp4 ^outpu.mkv # 实际输出档案名称为 "outpu.mkv"
※ 编辑: falcon (118.231.152.70 台湾), 04/26/2024 21:07:30
15F:→ smallreader: 档名.py可以直接呼叫档名不带.py 前面也不用叫python04/26 21:37
16F:→ smallreader: 我这边py档案都是打basename就能执行的耶04/26 21:46
17F:→ smallreader: PS> (Get-Command ffmpeg).path =>也会回传ffmpeg.py04/26 22:23
18F:→ smallreader: 只要副档名有注册用python开启就能这样替代04/26 22:24
19F:推 giacch: # Program.ps104/26 23:09
20F:→ giacch: Push-Location C:\Windows04/26 23:09
21F:→ giacch: & CMD.EXE /C "DIR NOTEPAD.EXE"04/26 23:09
22F:→ giacch: Pop-Location04/26 23:09
23F:推 giacch: 我忘了要传参数 04/26 23:37
24F:→ giacch: & CMD.EXE /C DIR $ARGS # CMD 那行改这样04/26 23:37
25F:→ giacch: Program.ps1 /B NOTEPAD.EXE # 这样执行04/26 23:38
26F:推 smallreader: 楼上..只要还经过cmd 答案就不及格04/27 00:22
27F:推 smallreader: 欸...不对啊,所以方法3用ps写不就解决了04/27 00:32
28F:→ smallreader: & "路径" $args #存成.ps104/27 00:35
我发现搞错一点了
还以为有名的参数一定要在 param() 内先宣告
不知道没有宣告的都会被丢到 $args
那就奇怪了...
微软的 winget 安装 console 程式怎麽怎麽不用 .ps1 档取代 links 目录下的符号连结
这样就不会因为程式找不到依赖档案而无法正常执行了
29F:推 giacch: 对呀 都能执行 CMD 了 就执行你想执行的程式就好啦04/27 00:38
30F:→ smallreader: 至於cd完全是多余动作,不需要 04/27 00:40
※ 编辑: falcon (118.231.152.70 台湾), 04/27/2024 00:59:44
31F:→ falcon: 虽然还不晓得会不会引发其他问题 04/27 01:03
32F:→ falcon: 总之,先感谢各位提供的方法 04/27 01:03
33F:推 smallreader: 更正,原来打basename认得到.py为可执行档,是因为系 04/27 02:17
34F:→ smallreader: 统变数的PATHEXT里面有包含.PY,然後虽然PATHEXT不包 04/27 02:17
35F:→ smallreader: 含.PS1,但因为壳层用PS,所以他自己会额外去找.PS104/27 02:17
对了 顺便问题下
遇到 Linux 移植过来的程式,那些 share 的档案要放哪里?
通常是 config 或 preset 档案
在 linux 中,我记得预设目录位置是
/usr/local/share/<program name>
使用时只要给程式档名或 basename 就可以了
但在 Windows 下如果不把工作目录切过去
好像就只能使用绝对路径了?
※ 编辑: falcon (118.231.152.70 台湾), 04/27/2024 03:27:27
36F:推 smallreader: 你的意思是/usr/local/bin吧,这个它有在PATH里面04/27 18:31
37F:→ smallreader: share就对到家目录的AppData04/27 18:37
试过许多路径与环境变数都没用
看起来有些程式并没有为 Windows 设定程式资料目录
只能在 .ps1 档内自动修改目标参数的值了
param ([Alias('pre')][string]$preset)
$programArgs = $args
if (-not !$preset) {
Push-Location 'C:\My Programs\program1\presets'
$absConfigPath =
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($preset)
Pop-Location
$programArgs += @('-pre', """$($absConfigPath)""")
}
& "C:\My Programs\program1\bin\program1.exe" $programArgs
exit $lastExitCode
※ 编辑: falcon (118.231.152.70 台湾), 04/28/2024 15:15:11
38F:→ hunandy14: 你那方法3 "cmd 的方式跳脱字元" 啥意思有范例吗04/29 11:54
39F:→ hunandy14: 然後环境被限制在cmd吗? 如果是powershell的话有其他解04/29 11:54
40F:→ hunandy14: 上面的pwsh 那样写有隐患 会被当作一大串字传递04/29 11:55
以 % 字元为例,在 cmd 必须 %% 来代表一个 % 常值字元
powershell 或 bash 中
ffmpeg -i input.mkv frame-%d.png
cmd 中
ffmpeg -i input.mkv frame-%%d.png
其他特殊字元若要表示为常值则是要在前面加上 ^ 字元
还有一些奇奇怪怪的例外除理方式...
关於 powershell 脚本
目前已发现的问题是在就发现就是使用了 param() 後
一旦传入了未在 param() 中宣告的参数并不会被存入 $args 变数中
而是会发生错误
「参数无法处理,因为 '???' 参数名称不明确......」
※ 编辑: falcon (39.10.17.37 台湾), 04/29/2024 22:56:27
41F:→ falcon: 但这个问题拿掉param(),从$args中找出目标参数的值取代为04/29 23:31
42F:→ falcon: 绝对路径就好了。我现在开始觉得用脚本管理环境变数PATH还04/29 23:31
43F:→ falcon: 比较实际点…04/29 23:31
44F:→ falcon: 之前就是那样做的04/29 23:31
45F:→ hunandy14: 一个比较无脑的办法是利用临时环境变数来处理04/30 09:56
46F:→ hunandy14: 要用bat难度在於转送参数,觉得一定会有东西丢失04/30 09:57
47F:→ hunandy14: 要是环境没限制在cmd说实在话ps1不用烦恼内建就有$args04/30 10:02
48F:→ hunandy14: 咦不对呀试了一下需要处理的只有引号,跳脱不用处理吧04/30 10:16
49F:→ falcon: bat的args一定跳脱字元,无论在cmd还是powershell环境中04/30 13:38
50F:→ falcon: 例如%^都是cmd中则是特殊字元,这导致ffmpeg输出frame为个04/30 13:38
51F:→ falcon: 图片时别无法将档名中的%d扩展为0,1,2,3...04/30 13:38
52F:→ falcon: 另外,我还发现ps1要实现pipeline功能似乎必须用到param()04/30 13:40
53F:→ falcon: 一旦使用param()就必须为爲目标程式所有参数也宣告变数04/30 13:44
例如这个例子执行时会发生错误
ffmpeg.exe -i input.mkv frame-%d.png
只会得到第一个画面的图片 frame-d.png
与输出路径格式错误讯息
必须让程式收到 args 长下面这个样子
ffmpeg.exe -i input.mkv frame-%%d.png
因为 % 在 cmd 中有特殊意义
例如 %~d1 为 bat 将 arg1 路径扩展为磁碟机代号
55F:→ hunandy14: param() 的问题就我前面跟你说的有隐患04/30 16:33
56F:→ hunandy14: 要嘛自己拆,要嘛用内建的$args一次收全部04/30 16:34
*[32m※ 编辑: falcon (39.10.17.37 台湾), 05/01/2024 11:50:37*[m
57F:→ hunandy14: 那是取决於你在哪里,如果在命令提示字元只需要一个% 05/01 13:40
58F:→ hunandy14: 如果在bat文档中由於读取的时候会解析,必须要%% 05/01 13:41
59F:→ hunandy14: %d应该是ffmpeg的功能而不是bat的功能,所以他只收一个 05/01 13:41
60F:→ hunandy14: %*确认可以收到参数了,应该是能证实代理是可行的 05/01 13:45
61F:→ hunandy14: 代理是转送不会过bat解析,理论上应该只需要输入一个 05/01 13:49
我理解你所说的,我之前也是这样做
但我再尝试了一次,没有发生错误
也找不到重现问题的关键点在哪里......
还是自动新增程式目录到 PATH 比较省事...
$programDirPaths = $env:PATHEXT -split ';' | % {"*$($_)"} |
Get-ChildItem -Include $_ -Recurse |
% {$_.DirectoryName} | Sort-Object -Unique
$newEnvPathVal = (([Environment]::GetEnvironmentVariable(
"Path", "Machine") -split ';') + $programDirPaths) -join ';'
[Environment]::SetEnvironmentVariable("Path", $newEnvPathVal, "Machine")
※ 编辑: falcon (39.10.17.37 台湾), 05/01/2024 21:30:08
63F:→ falcon: 我还发现 $p.StartInfo.FileName="basename" 不适用 bat 05/01 21:47
64F:推 hunandy14: 摁搞不定的话就直接无脑加吧 05/02 07:59