跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 挑战:用更简洁优美的方式将《程序员修炼之道》中的 Shell 命令转为 PowerShell
未分類
10 1 月 2021

挑战:用更简洁优美的方式将《程序员修炼之道》中的 Shell 命令转为 PowerShell

挑战:用更简洁优美的方式将《程序员修炼之道》中的 Shell 命令转为 PowerShell

資深大佬 : AndyAO 0

在《程序员修炼之道》的“变换式编程”章节中有这样一段 Shell 命令,其作用是列出当前目录中行数最多的 5 个文件。

$ find . -type f | xargs wc -l | sort -n | tail -6 | head -5 
     470 ./debug.pml      470 ./test_to_build.pml      487 ./dbc.pml      719 ./domain_languages.pml      727 ./dry.pml  

我算是 PowerShell 新手,折腾了老长时间之后终于写出来了,总感觉不如 Shell 的优雅,应该还有很大的优化空间。

(Get-ChildItem . | ForEach-Object {$_ | Select-Object -Property 'Name', @{label = 'Lines'; expression = {($_ | Get-Content).Length}}} |Sort-Object -Property 'Lines')|Select-Object -Last 5 

这里发出自己的答案,如果你有兴趣,而且有信心,能够写出更优雅简洁实现,可以来试试看。

当然不必使用别名和缩写来缩短长度,虽然看起来更简洁了,但损失了可读性。

“实话实说”好像是微软的一贯命名风格,比如说新线程在 Linux 里面用 fork,在 Windows 里面好像就是 creatProcess,感觉是微软的那个命名方法更加具有可读性,况且 PowerShell 中还能设置别名,想要多少短都行。

大佬有話說 (36)

  • 資深大佬 : hanxiV2EX

    shell 的更简洁吧

  • 資深大佬 : zhustec

    这么长的名称真的就优雅吗?要论命名,shell 也可以用 alias 命名出更具可读性的名字,也可以尽量使用两横的长参数。
    不见得 Get-Content 就比 cat 更具可读性,也不见得 Sort-Object 就比 sort 更具可读性。可读性也是要针对有一定 shell 基础的人来说的,也不能说只有一个一点都不懂 shell 的人能看懂才叫可读。

  • 主 資深大佬 : AndyAO

    @zhustec #2 当然不一定,因为那个词本身就没有严格的定义

  • 資深大佬 : felixcode

    PowerShell 这么长的命令,就很依赖自动补全了,而且分行才能显得足够直观,至于优雅,审美比较特别才会显得优雅吧。

  • 資深大佬 : zhuangzhuang1988

    还是直接 linq 把

  • 資深大佬 : geelaw

    Get-ChildItem -File | Sort-Object -Descending {
    $_ | Get-Content -ReadCount 1 |
    Measure-Object | Select-Object -ExpandProperty Count } |
    Select-Object -First 5

  • 資深大佬 : nightwitch

    在 Unix 发明的那个年代,由于硬件水平和软件水平的限制,不少 C 编译器只能识别最多 6-8 个字符的标志符。那个年代的“键盘”并不是现在的结构,你需要用重重的力气才能按下一个键,甚至还要靠纸带打孔机。所以 Unix 的 API 都是能短则短。Linux 作为 Unix 兼容的系统也把这些函数名字继承过来了。

    Windows 的前身 Dos 的发明也在 Unix 的十年以后了,而 Windows 本身则更迟了,无论是硬件还是软件的条件都好很多。

    Reference:
    https://unix.stackexchange.com/questions/214796/why-are-unix-posix-system-call-namings-so-illegible

  • 資深大佬 : zhuangzhuang1988

    “`C#
    var dir = “./obj”;
    var res = (from filePath in Directory.EnumerateFiles(dir)
    let lines = File.ReadAllLines(filePath).Count()
    orderby lines descending
    select (lines, filePath)).Take(5);
    foreach (var f in res)
    {
    Console.WriteLine(f);
    }
    “`

  • 資深大佬 : zhuangzhuang1988

    “`F#
    open System.Linq
    open System.IO

    let dir = “./obj”

    Directory.EnumerateFiles(dir)
    |> Seq.map (fun filePath -> (File.ReadAllLines(filePath).Count(), filePath))
    |> Seq.sortByDescending (fun (count, _) -> count)
    |> Seq.take 5
    |> Seq.iter (fun pair -> printfn “%A” pair)
    “`
    都比脚本好,清楚, 不鬼画符, 而且 F#, C#也可以当脚本用

  • 資深大佬 : FrankHB

    @nightwitch 那个说的是系统调用,用得没那么频繁,不用太在乎日常输入效率,反倒是不刻意习惯就容易读到一次恶心一次,强行习惯还会“没用的知识增加了”,自然要纠结 crate 还是 create 的问题。
    要说终端交互式使用,就算现在的键盘按起来没那么费劲,成本也不是零,而人干这活往往不会老实严格串行傻等反馈,所以输入一长就麻烦。补全也不是解决方案,因为前缀不长到一定程度,很难预测补出来的东西会是符合预期的,结果负载一大就该纠结“我这个命令到底输入到哪再补全比较好,万一不对该怎么操作”了,严重影响操作者的乱序执行效率,或者总是会增加人脑的调度负担。
    要是设计得像样就该被接受,scsh 不早该就把 POSIX shell 打得满地找牙了,而不是有自知之明“只适合写脚本”。
    所以到底是要简短还是要看使用场景是否要求交互式输入(一次性代码)。你要拿个终端输入命令,那就是输入顺手的短一点比较好,看着像鬼画符也忍了;你要考虑写重复使用的脚本或者被复用的脚本模块,那就更适合用看起来罗嗦点的更正经的编程语言。
    (推论:Perl 那种读起来呵呵写起来也快不到哪去的就老实凉凉吧。)
    这里的界限不是严格的,但使用场景本身就不太可能混起来,所以其实各种 POSIX shell 替代品都挺尴尬的——脚本可维护性上用脚打都能干死 shell,然而实在没法在保持适合写脚本“优雅”的情况下日用起来更简洁。

  • 資深大佬 : FrankHB

    也有少数情况写起来短是个优势,但并不是主要被接受的优势——短的写法可能更有可读性。典型例子是正则表达式中的最常用的语法:一看一个 * 就知道是重复 0 次或多次,换什么更长的写法来“可读”就都没个准了(大多数用户都未必知道这来自 Kleene 星号)。这个事实基于正则文法本身的简易以及这种约定的符号使用的广泛性上,以至于短的写法就是标准通用词汇,要强行展开成自动机程序搞得大脑暂存区溢出才是鬼画符。当然正则表达式(的不同方言)里面比较少用的旮旯就没那么有群众基础了(还有个问题就是符号不大够用),所以才会不得不有 [[:alpha:]] 之类明显画风不一样的叛徒……

  • 資深大佬 : msg7086

    Shell 就是为了简单和强力。换句话说,如果要我写冗长的 PS 代码,为什么我不写个 Python 或者 Ruby 脚本呢。

  • 資深大佬 : msg7086

    Ruby 版和#9 写的差不多。
    Dir.children(‘./obj’)
    .select{|f| File.file?(f)}
    .map{|f| [File.readlines(f).size, f]}
    .sort_by(&:first)
    .last(5)
    .each{|size, f| puts “#{size} #{f}”}

  • 資深大佬 : autoxbc

    能用 JS 写的最终都会用 JS 写

    const temp = [];
    for await( const { isFile , name } of Deno.readDir(‘./’) )
    {
    if(isFile)
    {
    const text = await Deno.readTextFile(`./${ name }`);
    temp.push([ text.match(/n/g).length , name ]);
    }
    }

    temp.sort( ([a],[b]) => a – b ).slice(-5).forEach( e => console.log(…e) );

  • 主 資深大佬 : AndyAO

    帖子是 PowerShell 挑战,没想到大部分人都不讨论 PowerShell,也许是这门脚本语言太小众了.

    谢谢 @zhuangzhuang1988 提供 F#和 C#版本,这两门语言和 PowerShell 是互通的,比较复杂的操作确实适合由他们,然后由 PowerShell 组合和调用.但我觉得’画鬼符’这种说法我还是要多解释几句:

    利用自动完成的特性,将所有的脚本写在一行,而且都用全称,在不熟悉的人看起来好像是’画鬼符’之类的,实际上敲击的速度很快,懂语法的人也不会觉得很乱.

    而且完全可以在脚本编辑器中展开来写,都用最常见的别称,那么我认为看起来会好很多:

    “`PowerShell
    dir $testPath -File | % {
    $_ | Select -P Name, @{
    l = ‘Lines’; e = {
    ($_ | cat).Length
    }
    }
    } | Sort -P L | Select -L 5
    “`

    “`OutPut
    Name Lines
    —- —–
    5.txt 5
    6.txt 6
    7.txt 7
    8.txt 8
    9.txt 9
    “`

    @autoxbc 谢谢你提供 JS 版本.不过这本书将的是’变换式编程’,所以最好还是用管道 /流机制.这是个带有变量的普通写法.

    @msg7086 早就听说 Ruby 很优美,谢谢你提供 Ruby 版本,开眼了!

  • 主 資深大佬 : AndyAO

    下面是代码的高亮版本和控制台的输出
    ![]( https://cdn.jsdelivr.net/gh/Andy-AO/GitHubPictureBed/img/20210110072303.png)
    ![]( https://cdn.jsdelivr.net/gh/Andy-AO/GitHubPictureBed/img/20210110072321.png)

  • 主 資深大佬 : AndyAO

    @zhuangzhuang1988 #9

    还有就是将 C#/F#当做脚本用是怎么用的?

    刚看到这句话的时候,我以为是用 PowerShell 调用写好的 C#代码,甚至做个 Cmdlet.但是我又突然想到,也许你的意思是直接当做脚本用.

    F#没了解过,但 C#这么做应该是不行的,因为它高度的模仿 Java,而后者是公认的非脚本语言,程序必须要放在类当中,有个明确的入口,而且还是静态类型,这些和脚本语言都不沾边,那么我想 C#也是.

    去网上大概的搜了一下,发现的确开始有这个倡议,但这还是 2019 年的事情.

    [C# 9: 迈向支持脚本编程的第一步-InfoQ]( https://www.infoq.cn/article/a0raqtmupssflaouigih)

    所以我想具体的了解一下,你指的是什么.

  • 主 資深大佬 : AndyAO

    @nightwitch #7
    谢谢你提供的宝贵信息,学习了!

    阐述自己观点的同时,带有信息的来源是个很好的习惯,是普通人没有的智慧.

  • 主 資深大佬 : AndyAO

    @FrankHB #10
    > 补全也不是解决方案,因为前缀不长到一定程度,很难预测补出来的东西会是符合预期的,结果负载一大就该纠结“我这个命令到底输入到哪再补全比较好,万一不对该怎么操作”了

    我说一下现在的 PowerShell 是如何解决此类问题的,目前应该有两个比较好的特性

    1. 补全的时候是渲染出列表的,所以根本就不会纠结于到底要打到什么程度,因为是一目了然的
    2. 根据 28 法则,大多数常用的命令,实际上只占 20%,甚至更少,PowerShell 是会学习你的历史记录给出建议的,所以很多时候打出几个字符就可以找到想要的命令,而且还可以顺便打出那些最常用的参数,这个敲一下键盘就可以完成

    当然这两个特性在 Windows 自带的 PowerShell 还没有或者是没有设为默认,需要新版本,而且需要在配置中自己调一下

  • 資深大佬 : Jirajine

    @FrankHB fish 岂不是更好,写起来简洁优雅,读起来也比 bash 清晰的多,没那些魔法。
    以及还有 elvish 、nushell 、ion 等更现代的 shell 。

  • 主 資深大佬 : AndyAO

    @geelaw
    谢谢你给出自己的答案,学到了很多.

    @autoxbc @msg7086 @FrankHB @zhuangzhuang1988 @felixcode
    嗨,伙计们,我终于想出更简洁优美的方式了,感到挺激动的.

    对于我来讲,这个写法已经比 Shell 有过之而无不及了!

    ![]( https://cdn.jsdelivr.net/gh/Andy-AO/GitHubPictureBed/img/20210110095800.png)

    dir -file | % {
    $_ | sort {
    ($_ | cat).length
    }
    } | select -l 5

  • 資深大佬 : venster

    @AndyAO 终于上了 alias 的大杀器了

  • 主 資深大佬 : AndyAO

    @venster #22
    我发现还有容易,这是新的,哈哈哈.

    @hanxiV2EX
    这次 Shell 更繁琐了,因为 PowerShell 只需要 3 个管道操作符号.
    而且最后获取的还是对象,能够进行更多的可扩展的操作.

    dir -file | sort {($_ | gc).length} | select -L 5

  • 資深大佬 : yannxia

    很多人熟悉了 Linux 的 Shell 了,这是先入为主的观念,如果说拿来编程那还是 Powershell 那套好一点,不过考虑到敲击的长度问题。

  • 資深大佬 : msg7086

    一般较为复杂的操作,还是以脚本语言为主。
    Shell 命令要的是 quick and dirty and powerful 。
    但是如果要长期用,一般都会选择整整齐齐地写成脚本。
    这时候,就可以根据实际的使用环境,选择自己喜欢的语言了。
    如果你是 Python 程序员这时候可以写个 Python 小程序,没有必要拘泥于使用某一种特定的 Shell 语言。

    PowerShell 的确是太小众了,除非是很特殊的环境(例如要跑在第三方的 Windows 服务器上,并且强制要求源代码公开),否则有无数其他的方案可以用(例如用 C 或者 Go 写小程序,装脚本语言,等等)。
    如果是自己用的话,还不如安装一下然后用自己熟悉的脚本语言来写代码了。
    PowerShell 也很少用来开发其他项目,所以熟悉的人不多,毕竟相当于新学一门语言了。

  • 主 資深大佬 : AndyAO

    @yannxia #24
    我现在已经找到非常好的写法了,不管是从”长度”,还是”操作次数”,还是”最终结果的可扩展性”都优于 Shell !

    之所以最初我那个命令比 Shell 繁琐,是因为我是个新手,不懂怎么样写出更好的,而那本书的作者是资深程序员.

    这是最简洁优美版本,可以成功运行获取拥有最多函数的 5 个文件对象.

    dir -file | sort {($_ | gc).l} | select -l 5

  • 主 資深大佬 : AndyAO

    @msg7086 #25

    赞同你的观点,这里补充几点.

    PowerShell 也是脚本语言,是面向对象的,因为它是.NET 的类,和.NET 体系完全互通.

    从 GitHub 的数据来看,依托于 Shell 的语言,Shell 是最流行的,其次就是 PowerShell.

    考虑到之前微软的封闭战略,PowerShell 在很长一段时间内都只能用于 Windows,而这个平台中用命令行的场景很少.

    但现在 PowerShell 已经可以在其他平台上使用了,而且已经完全开源,后面的发展还是很有希望的.

    https://madnight.github.io/githut/#/pull_requests/2020/4

    目前,大多数学 PowerShell 的人应该是.NET 或者 Windows 的系统管理员(运维人员),我学的目的主要是为了将命令行上的工作自动化.

  • 資深大佬 : darklowly

    @FrankHB 标识符和正则星号来类比在偷换概念

  • 資深大佬 : aloxaf

    看到这个问题我就知道肯定会引起不少争论 233 。我个人倒是挺喜欢 pwsh 的,最后的写法非常简洁,学习了

    我也来提供一个 zsh 下的写法:

    wc -l **/*(.) | sort -nr | sed -n ‘2,5p’

    小胜 4 个字符

  • 主 資深大佬 : AndyAO

    @aloxaf #29
    谢谢参与,本来这一篇帖子的主题,并不是争论什么 Shell/语言好,而是想征集一下有没有更好的 PowerShell 写法,看来只要是类似的,关于比较帖子都会比较有争议的.

    我有激动和急躁的毛病,最后犯了两个错误:

    首先是那个 Length 应该是不能省略的,我当时的测试出了点问题;

    其次就是字符统计也错了,我又重新尝试了一下,最长的就是那本书上的 Shell,一共是 58 个 字符,PowerShell 49 个,我看花眼了.

    然后你给出的 zsh 是 40 个,夺冠了,也让我了解了 zsh 的出色的简洁性,Thanks♪(・ω・)ノ

    ![]( https://cdn.jsdelivr.net/gh/Andy-AO/GitHubPictureBed/img/20210110134710.png)

  • 資深大佬 : wellsc

    反 人 类

  • 資深大佬 : Arnie97

    可读性不错,虽然我从没学过 PowerShell,能看懂这段程序在做什么。从第 2 条附言到第 3 条的那个优化有个专门的术语:Point-free

  • 資深大佬 : no1xsyzy

    建议投到 https://codegolf.stackexchange.com/

  • 資深大佬 : no1xsyzy

    话说起来,fork 跟 createProcess 不一样
    fork 是起一个完全一样的进程作为自己的子进程,要完成 createProcess 类似的工作还需要 exec
    或者采用 execvp,这就更不直观了

    而且有时还会因为覆盖了信号处理方式导致子程序发生 bug

    顺便来点 golf

    # Python REPL ( iPython ), 69 bytes
    import os;sorted((len([*open(f)]),str(f)) for f in os.scandir())[-5:]

  • 主 資深大佬 : AndyAO

    @no1xsyzy #33

    没必要,因为已经找到很完美的答案了,是我自己想出来的,在附言 3 上.

    我对 fork 和 createProcess 并不了解,只是在别处看到了这个例子,直接拿来用.谢谢你补充相关的内容.

  • 資深大佬 : no1xsyzy

    @AndyAO 投到 codegolf 你会看见一堆你听都没听说过的语言用十几个字节甚至几个字节解决这件事……
    没什么意义(

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具