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

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

  •  
  •   zeronofreya · 84 天前 · 5137 次点击
    这是一个创建于 84 天前的主题,其中的信息可能已经有所发展或是发生改变。

    很简单的调用:

    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 条附言  ·  83 天前
    不出意外,开始咬人了。

    @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  
       84 天前   ❤️ 3
    strings.IndexRune 建议好好看看函数签名,第二个参数是 rune 类型,你给了个字符串
    darrh00
        2
    darrh00  
       84 天前
    strings.IndexRune("学习", '习')
    ikaros
        3
    ikaros  
       84 天前
    啊? 第一个问题报错 file not found 应该很好一步步排查吧; 第二个错误也说了 cannot use "习" (untyped string constant) as rune value in argument to strings.IndexRune ,而且这个是编译期的问题,编辑器就能提示出来的吧;顺带我也觉得 go 有点烂,转 rust 吧
    haochen2
        4
    haochen2  
       84 天前
    go 标准库实现的质量非常高,希望能抱着学习的心态去探索她它
    keepeye
        5
    keepeye  
       84 天前
    哈哈 你之前用的啥? java 吗?
    fishCatcher
        6
    fishCatcher  
       84 天前 via iPhone   ❤️ 5
    来跟我一个字一个字读:cannot use untyped string constant as rune value in argument
    Mohanson
        7
    Mohanson  
       84 天前   ❤️ 10
    exec.Command 每一个 args 都要做一个参数传的, 正确写法是 `exec.Command("TeraCopy.exe", copyPaths, targetDir)`

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

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

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

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

    最后那个,分不清字符和字符串实在不是语言的问题
    CRVV
        14
    CRVV  
       83 天前   ❤️ 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  
       83 天前
    这种水平也来杠?
    ChrisFreeMan
        16
    ChrisFreeMan  
       83 天前 via iPhone
    go 的话题真活跃啊,问个 swift 的问题半天没人理
    thevita
        17
    thevita  
       83 天前   ❤️ 2
    比较适合些 PHP
    ----
    没有看不起 PHP 的意思,PHP 是最好的语言
    ysc3839
        18
    ysc3839  
       83 天前
    @CRVV 补充一下,类 Unix 系统传递的参数就是字符串数组,调用方怎么传递的,接收方可以原样接收到,不需要考虑引号、转义之类的问题。
    yyf1234
        19
    yyf1234  
       83 天前 via iPhone
    排错能力不太行呀兄弟
    0o0O0o0O0o
        20
    0o0O0o0O0o  
       83 天前 via iPhone
    我也至今没搞懂 rune ,所以会避免用…不过我明白这是因为自己菜,菜是因为自己懒不去读文档

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

    哈哈,建议 OP 慎重开喷,毕竟己不所欲勿施于人
    deplivesb
        27
    deplivesb  
       83 天前
    又菜又爱吹
    skiy
        28
    skiy  
       83 天前
    @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  
       83 天前 via iPhone
    @skiy 是的,不难理解,猜测也是类似于别的语言里统计 UTF-8 字符数的方式,如我在#20 的自我批评,就是懒...
    Vegetable
        30
    Vegetable  
       83 天前   ❤️ 4
    学而不思则罔,思而不学则殆。
    我经常想发这句话,但是实际很少发。我觉得你很需要这句话。
    hallDrawnel
        31
    hallDrawnel  
       83 天前
    第一个问题,把参数拆开成数组,交给基础库去完成拼接这才是跨平台的做法,你可以在 docker 、k8s 和各种 IDE 中发现都是这样输入的。

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

    你要是喷在点上那肯定是大家跟着你一起喷,但这明显是你菜啊。
    BeautifulSoap
        32
    BeautifulSoap  
       83 天前 via Android
    其他不发表意见,但关于三元运算符我坚决站在 Go 这边

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

    所以,我一碗水端平了,你现在应该对于我坚决反对三元运算符没意见了吧?
    skiy
        35
    skiy  
       83 天前
    你这知识面有点窄啊。。。

    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  
       83 天前
    @zeronofreya 对了,有的东西你真要比烂的话我觉得三元运算符 100%是要比 goto 更烂的
    比如 php 同时支持 goto 和三元运算符,但我 review 和参与的所有 php 项目没一个人在 php 中用过 goto ,而相应的在 php 中瞎几把乱用三元运算符的一大堆

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

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

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

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

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

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

    这样都能喷,来看看 Rust ,保证你一下午都编译不出来:)
    rekulas
        53
    rekulas  
       83 天前
    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  
       83 天前
    @singerll #40 大佬 我也是近期新学的 golang ,但是可能受其他语言影响,golang 设计的异常判断

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

    就是这种是标准的处理方法嘛?总感觉写起来怪怪的。
    GeruzoniAnsasu
        55
    GeruzoniAnsasu  
       83 天前
    @gam2046 golang 没有 exception ,没有「异常」。

    它只有「错误码」

    而且错误码是个字符串 hhhhh
    gam2046
        56
    gam2046  
       83 天前
    @GeruzoniAnsasu #55 唔,好吧。就是我比较不能适应的是这种写法

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

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

    我也不太明白是我理解不透彻嘛
    whyso
        57
    whyso  
       83 天前
    说实话,这种错误我都不好意思贴出来给大家看。。。
    lysS
        58
    lysS  
       82 天前
    @gam2046 try catch 其实就是 panic + recover
    lysS
        59
    lysS  
       82 天前
    还有三目,估计楼主没有写过业务。常用的三目大概长这样

    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  
       82 天前
    一个都没黑到点子上
    actar
        61
    actar  
       82 天前
    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  
       82 天前
    终端环境的参数两边的引号,是由终端进行处理的。传递进程序,是不带两边的引号的
    KousukeSakurako
        63
    KousukeSakurako  
       82 天前 via iPhone
    怎么回事,黑不到点子上啊我看着都急
    kkbblzq
        64
    kkbblzq  
       82 天前
    说实话,前半段看下来还好,但是你加了后半段,看起来就很尴尬了,因为非常明显的报错😂,感觉这不是 go 不 go 的问题了,换个语言报错你确定你能看得出来?
    borpubi
        65
    borpubi  
       80 天前
    对于一个伪 IT 来说,三元运算符确实没有 if 易读易懂好解释。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3537 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 10:10 · PVG 18:10 · LAX 03:10 · JFK 06:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.