V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zeronofreya
V2EX  ›  程序员

被 golang 坑了一下午, win 平台无法正常调用外部程序看这里。

  •  
  •   zeronofreya · 2022-04-08 17:33:43 +08:00 · 6356 次点击
    这是一个创建于 720 天前的主题,其中的信息可能已经有所发展或是发生改变。

    很简单的调用:

    cmd := exec.Command( "TeraCopy.exe", fmt.Sprintf(`*"%s" "%s"`, copyPaths, targetDir) )
    err := cmd.Start()
    

    报错:

    ---------------------------
     TeraCopy - Error
    ---------------------------
    File not found:
    \e:\**\tc.tmp \E:\**\b\
    ---------------------------
    确定   
    ---------------------------
    

    ??? 啥玩意 ???

    本着出了问题先找自身原因的混帐话优良传统,控制台与 TeraCopy 软件试了各种参数,都正常……

    感觉是转义出了问题,谷歌了半天,除了复制粘贴就没别的了。

    米田共里淘金终于发现了一片文章:Go 在 windows 上调用本地进程传参时的一个天坑

    MD ,最终 tm 还是 go 的问题,一直不敢往那想,属实被喷怕了。 摘抄一下:

    On Windows, processes receive the whole command line as a single string and do their own parsing. Command combines and quotes Args into a command line string with an algorithm compatible with applications using CommandLineToArgvW (which is the most common way). Notable exceptions are msiexec.exe and cmd.exe (and thus, all batch files), which have a different unquoting algorithm. In these or other similar cases, you can do the quoting yourself and provide the full command line in SysProcAttr.CmdLine, leaving Args empty.

    反正我看不懂,看人家的解释:

    也就是说,针对 cmd 参数加的引号参数会有不同的逻辑,必须在 SysProcAttr.CmdLine 中写入原始参数了,但是 Args 留空,又会导致 SysProcAttr 值为 nil ,所以简单赋值也是不行的

    改了一下代码:

    cmdLine := fmt.Sprintf(`copy *"%s" "%s"`, copyPaths, targetDir)
    cmd := exec.Command("TeraCopy.exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/c " + cmdLine}
    err := cmd.Start()
    

    解决。

    学习 go 一段时间,觉得它的开发者很矛盾,比如三元运算符,很多人都想要它,但官方却以语法统一、可能会导致阅读困难之类的理由推脱。 但

    return r ? true : false
    

    不比你 if 要整洁易读? 对多字节的处理也很费劲,我到现在不知道怎样查找某个中文的位置,IndexRune 报错,也没谷歌到答案

    fmt.Println(strings.IndexRune("学习", "习"))
    
    
    Error
    ./prog.go:9:42: cannot use "习" (untyped string constant) as rune value in argument to strings.IndexRune
    

    是不是有人要说,爱用用,不用滚呢?

    第 1 条附言  ·  2022-04-08 22:19:20 +08:00
    不出意外,开始咬人了。

    @CRVV 是我打的时候漏打了 copy ,这里很抱歉,但只是这里,代码里可没漏。虽然你没说具体怎么写,但我猜你是想说自己把参数分开写吧?
    > 然后是把两个参数拼到了一个参数里面
    当然我试过了。
    ```
    cmd := exec.Command("TeraCopy.exe", "copy", `*"e:\1.txt"`, `"e:\a\"`)
    ```

    ```
    ---------------------------
    TeraCopy - Error
    ---------------------------
    File not found:
    \e:\1.txt\
    ---------------------------
    确定
    ---------------------------
    ```
    可以看到参数不正常, 当然,
    ```
    cmd := exec.Command("TeraCopy.exe", `copy *"e:\1.txt" "e:\a\"`)
    ```
    也是会报错的,基本就是'"'被弄成了‘\’


    @haochen2 我知道它要 rune ,但不知道怎么转,就直接写了,网上说是用`[]rune(str)`,但还是报错

    ```
    cannot use []rune("学习") (value of type []rune) as type string in argument to strings.IndexRune
    ```
    所以才说很费劲



    @Mohanson 彼此彼此,自己不试就张嘴就来,也挺讨厌呢
    65 条回复    2022-04-12 16:37:12 +08:00
    haochen2
        1
    haochen2  
       2022-04-08 17:39:26 +08:00   ❤️ 3
    strings.IndexRune 建议好好看看函数签名,第二个参数是 rune 类型,你给了个字符串
    darrh00
        2
    darrh00  
       2022-04-08 17:45:38 +08:00
    strings.IndexRune("学习", '习')
    ikaros
        3
    ikaros  
       2022-04-08 17:46:42 +08:00
    啊? 第一个问题报错 file not found 应该很好一步步排查吧; 第二个错误也说了 cannot use "习" (untyped string constant) as rune value in argument to strings.IndexRune ,而且这个是编译期的问题,编辑器就能提示出来的吧;顺带我也觉得 go 有点烂,转 rust 吧
    haochen2
        4
    haochen2  
       2022-04-08 17:47:13 +08:00
    go 标准库实现的质量非常高,希望能抱着学习的心态去探索她它
    keepeye
        5
    keepeye  
       2022-04-08 17:52:29 +08:00
    哈哈 你之前用的啥? java 吗?
    fishCatcher
        6
    fishCatcher  
       2022-04-08 17:55:56 +08:00 via iPhone   ❤️ 5
    来跟我一个字一个字读:cannot use untyped string constant as rune value in argument
    Mohanson
        7
    Mohanson  
       2022-04-08 17:57:41 +08:00   ❤️ 10
    exec.Command 每一个 args 都要做一个参数传的, 正确写法是 `exec.Command("TeraCopy.exe", copyPaths, targetDir)`

    另外真的很烦你这种人...
    v2kt
        8
    v2kt  
       2022-04-08 17:58:39 +08:00
    cannot use untyped string constant as rune value in argument
    ck65
        9
    ck65  
       2022-04-08 17:59:31 +08:00
    对,会有人说的。
    Trim21
        10
    Trim21  
       2022-04-08 18:04:30 +08:00 via Android
    exec.Command 的 args 是直接传进去的,不需要用 fmt 再拼接一下,这地方不是 shell 还会解析你的命令行。(这个问题在其他语言其实也有类似的)

    string 和 rune 不是一个东西。
    sqfphoenix
        11
    sqfphoenix  
       2022-04-08 18:04:42 +08:00
    cmd := exec.Command("notepad.exe", filepath.Join("D:/", "metrics.txt"))
    使用 filepath 很难吗
    使用 cmd.String()输出看看自己的命令很难吗
    看一下 strings 的源码很难吗
    lance6716
        12
    lance6716  
       2022-04-08 18:14:36 +08:00 via Android
    无力吐槽,再说两个

    return r ? true : false 跟 return r 有什么区别?

    调用 exec.Command 之前不会看一眼他的函数注释?
    ScepterZ
        13
    ScepterZ  
       2022-04-08 18:16:46 +08:00
    实习那年遇到过这个问题,后来发现是每个 args 要单独传,不能自己拼起来

    最后那个,分不清字符和字符串实在不是语言的问题
    CRVV
        14
    CRVV  
       2022-04-08 18:18:54 +08:00   ❤️ 14
    这个传参数的问题实际上是 Windows 的问题,我大概解释一下

    首先,大家通常理解的参数,是一个数组。
    C 语言里面会写 int main( int argc, char* argv[] ),Java 会写 public static void main(String[] args),参数都是数组。
    所以 Go 的 exec.Command 是 func Command(name string, arg ...string) *Cmd 。arg 也是数组,写 Command("echo", "a", "b", "c") 就是传了 3 个参数给 echo 。

    但是,Windows 里面的参数是一个字符串,也就是 Go 的文档里面说的 “On Windows, processes receive the whole command line as a single string”
    Windows 里面的程序在接收到这个大字符串以后,再自己把它拆成数组,得到大家通常理解的一串参数。
    问题在于,Windows 不同的程序有不同的方法来拆这个字符串,所以拆出来的数组也不一样。
    这里 Go 的行为是说,如果你在 Command 里传了一个数组的参数,那么 Go 就按照 Windows 通常的拆法把这些参数拼起来,但是 Windows 上的 cmd.exe 和 msiexec.exe 的拆法并不是那个通常的拆法,所以会出问题。如果你要调用的程序是 cmd.exe ,那么 Go 拼字符串的算法就是错的,请自己把字符串拼好了放到 SysProcAttr 里面。

    但是楼主调用的程序不是 cmd.exe ,也就是说楼主遇到的问题根本不是这个问题。

    我能看出来的问题,首先是一开始楼主少写了一个 copy ,然后是把两个参数拼到了一个参数里面,那当然不能用了。
    这个问题和 Go 和 Windows 都没有关系,纯粹是楼主自己的问题。

    把这个事讲完,把两个参数拼到一个参数里面的意思是

    cat a b 是说把 a 和 b 两个文件的内容打出来
    cat "a b" 是说有一个文件的文件名是 a 空格 b ,把这一个文件的内容打出来,如果你想要的是前一种,这样写就会报错说找不到文件
    cassyfar
        15
    cassyfar  
       2022-04-08 18:21:13 +08:00
    这种水平也来杠?
    ChrisFreeMan
        16
    ChrisFreeMan  
       2022-04-08 18:23:55 +08:00 via iPhone
    go 的话题真活跃啊,问个 swift 的问题半天没人理
    thevita
        17
    thevita  
       2022-04-08 18:29:41 +08:00   ❤️ 2
    比较适合些 PHP
    ----
    没有看不起 PHP 的意思,PHP 是最好的语言
    ysc3839
        18
    ysc3839  
       2022-04-08 18:42:43 +08:00
    @CRVV 补充一下,类 Unix 系统传递的参数就是字符串数组,调用方怎么传递的,接收方可以原样接收到,不需要考虑引号、转义之类的问题。
    yyf1234
        19
    yyf1234  
       2022-04-08 18:47:24 +08:00 via iPhone
    排错能力不太行呀兄弟
    0o0O0o0O0o
        20
    0o0O0o0O0o  
       2022-04-08 18:55:35 +08:00 via iPhone
    我也至今没搞懂 rune ,所以会避免用…不过我明白这是因为自己菜,菜是因为自己懒不去读文档

    我偶尔用 go 写一些 windows 的小工具,遇到过一些真正的坑,不过我对平台相关或是 cgo 相关的坑都比较宽容…
    ec0
        21
    ec0  
       2022-04-08 18:57:18 +08:00
    fmt.Println(strings.IndexRune("学习", '习')) 为什么返回的是 3 ,我以为是 1
    KaynW
        22
    KaynW  
       2022-04-08 18:59:48 +08:00
    无力吐槽...
    KaynW
        23
    KaynW  
       2022-04-08 19:03:41 +08:00
    @ec0 中文字符在 utf-8 下占 3 个字节
    Tink
        24
    Tink  
       2022-04-08 19:04:22 +08:00 via Android
    真的无力吐槽
    Jarvis666
        25
    Jarvis666  
       2022-04-08 19:22:26 +08:00
    想黑 win ,又想黑 go ,又质疑人家跨平台开发的能力,啧啧
    listenerri
        26
    listenerri  
       2022-04-08 19:43:36 +08:00 via Android   ❤️ 1
    “属实被喷怕了”

    哈哈,建议 OP 慎重开喷,毕竟己不所欲勿施于人
    deplivesb
        27
    deplivesb  
       2022-04-08 19:51:05 +08:00
    又菜又爱吹
    skiy
        28
    skiy  
       2022-04-08 20:56:45 +08:00
    @0o0O0o0O0o rune 是 int32 的别名,这个有什么难理解的?换个名称也一样的使用方式。

    rune is an alias for int32 and is equivalent to int32 in all ways.
    https://github.com/golang/go/blob/5a90270d7f5b384de31399133c7336d007fbd93d/src/builtin/builtin.go#L92
    0o0O0o0O0o
        29
    0o0O0o0O0o  
       2022-04-08 21:28:26 +08:00 via iPhone
    @skiy 是的,不难理解,猜测也是类似于别的语言里统计 UTF-8 字符数的方式,如我在#20 的自我批评,就是懒...
    Vegetable
        30
    Vegetable  
       2022-04-08 21:37:07 +08:00   ❤️ 4
    学而不思则罔,思而不学则殆。
    我经常想发这句话,但是实际很少发。我觉得你很需要这句话。
    hallDrawnel
        31
    hallDrawnel  
       2022-04-08 21:51:53 +08:00
    第一个问题,把参数拆开成数组,交给基础库去完成拼接这才是跨平台的做法,你可以在 docker 、k8s 和各种 IDE 中发现都是这样输入的。

    第二点是纯粹连编译错误都不看。

    你要是喷在点上那肯定是大家跟着你一起喷,但这明显是你菜啊。
    BeautifulSoap
        32
    BeautifulSoap  
       2022-04-08 22:02:58 +08:00 via Android
    其他不发表意见,但关于三元运算符我坚决站在 Go 这边

    莫非定律了解下。只要语言给你用三元运算符,那绝对有程序员会拿它写出正常人没法轻松理解的代码。
    比如每当看见 php 项目代码里有人把三元运算符玩出花的时候,我心里都是一万句草泥马
    zeronofreya
        33
    zeronofreya  
    OP
       2022-04-08 22:24:34 +08:00   ❤️ 1
    @BeautifulSoap 那 goto 有何见解?
    BeautifulSoap
        34
    BeautifulSoap  
       2022-04-08 22:42:16 +08:00
    @zeronofreya ?那还用问?当然是讨厌+反对 goto 咯,还有别的可能性?(当然 Go 一些功能你不用 goto 没法实现,有时候不得不捏着鼻子用)

    所以,我一碗水端平了,你现在应该对于我坚决反对三元运算符没意见了吧?
    skiy
        35
    skiy  
       2022-04-08 22:48:10 +08:00
    你这知识面有点窄啊。。。

    goto 是部分语言的一个特征。

    C 语言的:
    https://www.runoob.com/cprogramming/c-goto-statement.html

    C++ 的:
    https://www.runoob.com/cplusplus/cpp-goto-statement.html

    C# 的:
    https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/statements/jump-statements#the-goto-statement

    JAVA 虽然没有实现,但是保留了这个关键字。
    BeautifulSoap
        36
    BeautifulSoap  
       2022-04-08 22:57:07 +08:00
    @zeronofreya 对了,有的东西你真要比烂的话我觉得三元运算符 100%是要比 goto 更烂的
    比如 php 同时支持 goto 和三元运算符,但我 review 和参与的所有 php 项目没一个人在 php 中用过 goto ,而相应的在 php 中瞎几把乱用三元运算符的一大堆

    比起 goto ,三元运算符是一个很容易让写代码的人产生迷之自信,让人有一种我写出的“简洁”代码很酷的错觉
    liuzy1999
        37
    liuzy1999  
       2022-04-08 23:00:57 +08:00
    建议楼主加强英语能力与耐心,你会发现这些问题会改善很多的。
    surbomfla
        38
    surbomfla  
       2022-04-08 23:13:08 +08:00 via Android
    无论是百度还是必应 搜索 strings.IndexRune 都有正确的用法示例,op 是怎么找到 `[]rune(str)` 这种用法的?
    Tink
        39
    Tink  
       2022-04-09 02:45:20 +08:00 via Android
    你这个排错和搜索能力还需要提高
    singerll
        40
    singerll  
       2022-04-09 06:04:36 +08:00 via Android
    不是针对 go ,三元运算符你说易读我真的不认同。
    还有第一个问题,本人非专业开发运维人员,偶尔写点小脚本,我用 python 的 subprocess 模块也有这个问题,第一次用也是调了好久,说白了就是懒得扒拉官方文档,随便搜个教程开始写,最后出问题排除就是比较麻烦。。。
    xuanbg
        41
    xuanbg  
       2022-04-09 06:32:42 +08:00
    @BeautifulSoap 连 Java 这种古板的语言都支持三元呢。。。
    holulu
        42
    holulu  
       2022-04-09 06:52:04 +08:00
    你摘的英文文章说得挺详细的。
    yulon
        43
    yulon  
       2022-04-09 06:58:53 +08:00
    为啥要用格式化组合参数啊,那么喜欢用格式化来写 C 吧。

    Go 天生支持 Rune ,Windows 下还自动给你转码 Unicode ,居然说多语言不好处理,那来试试 C++ 吧。

    怎么三元又能出来鞭尸一顿,那来写 C/C++ 啊,C 系的语法全都有吧。

    什么爱用用咬不咬人,C++ 用好了绝对比用 Go 要爽。

    所以快来写 C++ 啊!所以快来写 C++ 啊!所以快来写 C++ 啊!

    你为什么还不来万能的 C++ 呢?
    zjyl1994
        44
    zjyl1994  
       2022-04-09 09:44:41 +08:00 via iPhone
    天,大佬你学学英文吧,实在不行装个词典软件,编译器说的很明白了。这都不想弄,要不试试中文编程?
    tomolo
        45
    tomolo  
       2022-04-09 09:51:12 +08:00
    所以程序员还是需要学一点英语的
    whileFalse
        46
    whileFalse  
       2022-04-09 09:55:21 +08:00 via iPhone
    反正是有那种在三元里面套三元的傻*,比如我。
    怎么说呢,我是运维但写了二十年代码,看别的运维的代码都是平趟,别人看我代码那就祝你好运吧,要是哪天不高兴写了个复杂的表达式就够你研究一会的,但对于我来说看懂类似的表达式跟看明白注释用时没啥区别。
    所以,go 不让你用这种 c 遗留下来的语法是为你好啊孩子,我们这种老油条真的无所谓的。
    carlclone
        47
    carlclone  
       2022-04-09 10:12:41 +08:00
    不如先找找自己水平问题再甩锅?
    adoal
        48
    adoal  
       2022-04-09 10:13:26 +08:00 via iPhone
    连三元运算符都能吵起来,那 if expression 你们是不是要当魔鬼了
    fauieh32fihe
        49
    fauieh32fihe  
       2022-04-09 10:41:45 +08:00
    这种货色。。。哈哈
    debuginn
        50
    debuginn  
       2022-04-09 12:25:29 +08:00
    @haochen2 认同。go 的标准库就是一股清流
    yin1999
        51
    yin1999  
       2022-04-09 12:45:42 +08:00
    我真的是无语了,错误提示真有那么难看懂吗,strings.IndexRune 这个函数的第一个参数用的字符串,第二个参数用 rune ,要调用也应该是些 strings.IndexRune("学习", '习') 这么写,也就是 2 楼给出的答案,咋还能写成 strings.IndexRune([]rune("学习"), xxxx) 这样的呢(看你回复里面给出的错误提示都知道你的第一个参数填的啥了,这还不够明白吗)
    mengzhuo
        52
    mengzhuo  
       2022-04-09 13:48:32 +08:00
    额,感觉还是没仔细看错误提示……

    这样都能喷,来看看 Rust ,保证你一下午都编译不出来:)
    rekulas
        53
    rekulas  
       2022-04-09 14:17:23 +08:00
    TeraCopy - Error
    错误已经很明显了,被调用程序报的错,这都能怪到 go 的头上,你自己都乱喷就别怪别人喷你了
    首先确保你参数正确,尽量用 git bash 调试好正确参数,然后复制到代码里基本就可用-少数可能需要微调
    ("TeraCopy.exe", "copy", `*"e:\1.txt"`, `"e:\a\"`)
    没用过 tera ,不知道*"e:\1.txt" 为什么要这样写,但是双引号里可能需要转义才对,例如 e:\\1.txt
    `"e:\a\"` ,这种写法 我觉得就是错误的,直接`e:\a\` 说不定还可以

    如果双引号是程序必须的参数,可以试试 "copy", `*"e:\\1.txt"` `e:\a\`
    你最好发一遍可以正确执行的命令,大家可以帮你看看
    最后再重申这跟 golang 无关,你用其他语言也可能遇到一样的问题
    gam2046
        54
    gam2046  
       2022-04-09 15:19:48 +08:00
    @singerll #40 大佬 我也是近期新学的 golang ,但是可能受其他语言影响,golang 设计的异常判断

    if _, err := something; err == nil {...}

    就是这种是标准的处理方法嘛?总感觉写起来怪怪的。
    GeruzoniAnsasu
        55
    GeruzoniAnsasu  
       2022-04-09 15:26:10 +08:00
    @gam2046 golang 没有 exception ,没有「异常」。

    它只有「错误码」

    而且错误码是个字符串 hhhhh
    gam2046
        56
    gam2046  
       2022-04-09 15:56:19 +08:00
    @GeruzoniAnsasu #55 唔,好吧。就是我比较不能适应的是这种写法

    if _,err:= ... ; err != nil{ ... }

    一个方法中有好多 if err != nil ,相比其他语言的 try catch ,一些兜底的错误处理,可以统一放在 catch 里。

    我也不太明白是我理解不透彻嘛
    whyso
        57
    whyso  
       2022-04-09 17:46:02 +08:00
    说实话,这种错误我都不好意思贴出来给大家看。。。
    lysS
        58
    lysS  
       2022-04-09 19:09:04 +08:00
    @gam2046 try catch 其实就是 panic + recover
    lysS
        59
    lysS  
       2022-04-09 19:10:54 +08:00
    还有三目,估计楼主没有写过业务。常用的三目大概长这样

    const word = (res.distance === 0) ? 'a'
    : (res.distance === 1 && res.difference > 3) ? 'b'
    : (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c'
    : 'd';
    fyibmsd
        60
    fyibmsd  
       2022-04-09 19:32:18 +08:00
    一个都没黑到点子上
    actar
        61
    actar  
       2022-04-09 21:29:49 +08:00
    const TeraCopy = "D:\\TeraCopy\\TeraCopy.exe"

    func main() {
    copyPaths := os.Args[1]
    targetDir := os.Args[2]
    command := exec.Command(TeraCopy, "copy", fmt.Sprintf("*%s", copyPaths), targetDir)
    if err := command.Run(); err != nil {
    panic(err)
    }
    }


    可以这样写,亲测可行

    TeraCopy.exe copy *"G:\1 source.txt" "G:\target\"
    等价于
    TeraCopy.exe copy "*G:\1 source.txt" "G:\target\"

    exec.Command 传递参数的时候,不需要引号括起来
    actar
        62
    actar  
       2022-04-09 21:32:54 +08:00
    终端环境的参数两边的引号,是由终端进行处理的。传递进程序,是不带两边的引号的
    KousukeSakurako
        63
    KousukeSakurako  
       2022-04-09 23:54:29 +08:00 via iPhone
    怎么回事,黑不到点子上啊我看着都急
    kkbblzq
        64
    kkbblzq  
       2022-04-10 07:56:48 +08:00
    说实话,前半段看下来还好,但是你加了后半段,看起来就很尴尬了,因为非常明显的报错😂,感觉这不是 go 不 go 的问题了,换个语言报错你确定你能看得出来?
    borpubi
        65
    borpubi  
       2022-04-12 16:37:12 +08:00
    对于一个伪 IT 来说,三元运算符确实没有 if 易读易懂好解释。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   952 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 21:48 · PVG 05:48 · LAX 14:48 · JFK 17:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.