【问题标题】:How do I use Ruby for shell scripting?如何使用 Ruby 编写 shell 脚本?
【发布时间】:2023-09-26 13:46:01
【问题描述】:

我有一些简单的 shell 脚本任务想要完成

例如:从匹配某个正则表达式的文件列表中选择工作目录中的文件。

我知道我可以使用标准的 bash 和 grep 来做这种事情,但是我很高兴能够破解可以在 windows 和 linux 中运行的快速脚本,而无需我记住一堆命令行程序和标志等等

我试图完成这项工作,但最终对我应该从哪里获取信息(例如对当前目录的引用)感到困惑

所以问题是我需要了解 Ruby 库的哪些部分才能编写 ruby​​ shell 脚本?

【问题讨论】:

标签: ruby shell scripting


【解决方案1】:

“我如何编写 ruby​​”有点超出 SO 的范围。

但是要将这些 ruby​​ 脚本转换为可执行脚本,请将其作为 ruby​​ 脚本的第一行:

#!/path/to/ruby

然后使文件可执行:

chmod a+x myscript.rb

你走了。

【讨论】:

    【解决方案2】:

    假设您编写了 script.rb 脚本。放:

    #!/usr/bin/env ruby
    

    作为第一行并做一个chmod +x script.rb

    【讨论】:

      【解决方案3】:

      把它放在你 script.rb 的开头

      #!/usr/bin/env ruby
      

      然后将其标记为可执行:

      chmod +x script.rb
      

      【讨论】:

        【解决方案4】:

        正如其他人已经说过的,你的第一行应该是

        #!/usr/bin/env ruby
        

        您还必须使其可执行:(在 shell 中)

        chmod +x test.rb
        

        然后是 ruby​​ 代码。如果你打开一个文件

        File.open("file", "r") do |io|
            # do something with io
        end
        

        文件将在您在 shell 中使用 pwd 获得的当前目录中打开。

        脚本的路径也很容易获得。使用$0,您将获得shell 的第一个参数,即脚本的相对路径。绝对路径可以这样确定:

        #!/usr/bin/env ruby
        require 'pathname'
        p Pathname.new($0).realpath()
        

        对于文件系统操作,我几乎总是使用路径名。这是许多其他文件系统相关类的包装器。也很有用:目录、文件...

        【讨论】:

          【解决方案5】:

          在 ruby​​ 中,常量 __FILE__ 将始终为您提供正在运行的脚本的路径。

          在 Linux 上,/usr/bin/env 是你的朋友:

          #! /usr/bin/env ruby
          # Extension of this script does not matter as long
          # as it is executable (chmod +x)
          puts File.expand_path(__FILE__)
          

          在 Windows 上,这取决于 .rb 文件是否与 ruby​​ 相关联。 如果是:

          # This script filename must end with .rb
          puts File.expand_path(__FILE__)
          

          如果不是,您必须在它们上显式调用 ruby​​,我使用中间 .cmd 文件:

          my_script.cmd:

          @ruby %~dp0\my_script.rb
          

          my_script.rb:

          puts File.expand_path(__FILE__)
          

          【讨论】:

            【解决方案6】:

            去获取Everyday Scripting with Ruby 的副本。它有很多有用的提示,告诉你如何去做你想做的事情。

            【讨论】:

            • 好书,我现在正在阅读:感觉就像一次禅宗代码之旅。如果您不了解 TDD,您将一路学习 TDD 的基础知识。
            • 我认为这本书有一些很好的信息,但对于有经验的程序员来说开销很大。
            【解决方案7】:

            默认情况下,您已经可以访问DirFile,它们本身就非常有用。

            Dir['*.rb'] #basic globs
            Dir['**/*.rb'] #** == any depth of directory, including current dir.
            #=> array of relative names
            
            File.expand_path('~/file.txt') #=> "/User/mat/file.txt"
            File.dirname('dir/file.txt') #=> 'dir'
            File.basename('dir/file.txt') #=> 'file.txt'
            File.join('a', 'bunch', 'of', 'strings') #=> 'a/bunch/of/strings'
            
            __FILE__ #=> the name of the current file
            

            在 stdlib 中也有用的是 FileUtils

            require 'fileutils' #I know, no underscore is not ruby-like
            include FileUtils
            # Gives you access (without prepending by 'FileUtils.') to
            cd(dir, options)
            cd(dir, options) {|dir| .... }
            pwd()
            mkdir(dir, options)
            mkdir(list, options)
            mkdir_p(dir, options)
            mkdir_p(list, options)
            rmdir(dir, options)
            rmdir(list, options)
            ln(old, new, options)
            ln(list, destdir, options)
            ln_s(old, new, options)
            ln_s(list, destdir, options)
            ln_sf(src, dest, options)
            cp(src, dest, options)
            cp(list, dir, options)
            cp_r(src, dest, options)
            cp_r(list, dir, options)
            mv(src, dest, options)
            mv(list, dir, options)
            rm(list, options)
            rm_r(list, options)
            rm_rf(list, options)
            install(src, dest, mode = <src's>, options)
            chmod(mode, list, options)
            chmod_R(mode, list, options)
            chown(user, group, list, options)
            chown_R(user, group, list, options)
            touch(list, options)
            

            挺好看的

            【讨论】:

              【解决方案8】:

              这也可能有帮助:http://rush.heroku.com/

              我用的不多,但看起来很酷

              来自网站:

              rush 是使用纯 Ruby 语法的 unix shell(bash、zsh 等)的替代品。 grep 遍历文件、查找和终止进程、复制文件 - 您在 shell 中所做的一切,现在都在 Ruby 中

              【讨论】:

              【解决方案9】:

              这是其他答案中缺少的重要内容:命令行参数通过 ARGV(全局)数组暴露给您的 Ruby shell 脚本。

              所以,如果你有一个名为 my_shell_script 的脚本:

              #!/usr/bin/env ruby
              puts "I was passed: "
              ARGV.each do |value|
                puts value
              end
              

              ...使其可执行(正如其他人提到的):

              chmod u+x my_shell_script
              

              然后这样称呼它:

              > ./my_shell_script one two three four five
              

              你会得到这个:

              I was passed: 
              one
              two
              three
              four
              five
              

              参数与文件名扩展配合得很好:

              ./my_shell_script *
              
              I was passed: 
              a_file_in_the_current_directory
              another_file    
              my_shell_script
              the_last_file
              

              其中大部分仅适用于 UNIX(Linux、Mac OS X),但您可以在 Windows 中执行类似(尽管不太方便)的操作。

              【讨论】:

                【解决方案10】:

                这里有很多好的建议,所以我想再补充一点。

                1. 反引号(或反引号)让您更轻松地编写脚本。考虑

                  puts `find . | grep -i lib`
                  
                2. 如果您在获取反引号的输出时遇到问题,则这些内容将变为标准错误而不是标准输出。 Use this advice

                  out = `git status 2>&1`
                  
                3. 反引号做字符串插值:

                  blah = 'lib'
                  `touch #{blah}`
                  
                4. You can pipe inside Ruby, too。这是我博客的链接,但它链接回这里,所以没关系:)关于这个主题可能有更高级的东西。

                5. 正如其他人所说,如果你想认真一点,可以使用 Rush:不仅仅是作为 shell 替代品(这对我来说有点太滑稽了),而且还有 as a library for your use in shell scripts and programs.


                在 Mac 上,在 Ruby 中使用 Applescript 以获得更多功能。这是我的shell_here 脚本:

                #!/usr/bin/env ruby
                `env | pbcopy` 
                cmd =  %Q@tell app "Terminal" to do script "$(paste_env)"@
                puts cmd
                
                `osascript -e "${cmd}"`
                

                【讨论】:

                • 我只需要将代码缩进 4 个空格,就可以对其进行格式化。我也把反引号放回去了,但我一点也不了解 Ruby,所以你需要检查以确保它是你想要的。
                • @Bill the Lizard,是的,这就是我需要的“技巧”:双缩进。感谢您的帮助。
                【解决方案11】:

                webmat 的答案是完美的。我只是想给你指出一个补充。如果你必须为你的脚本处理很多命令行参数,你应该使用optparse。它很简单,对您有很大帮助。

                【讨论】:

                  【解决方案12】:

                  当您想编写更复杂的 ruby​​ 脚本时,这些工具可能会有所帮助:

                  例如:

                  • thor(一个脚本框架)

                  • gli(类似git的界面)

                  • methadone(用于创建简单工具)

                  它们让您快速开始编写自己的脚本,尤其是“命令行应用程序”。

                  【讨论】:

                    【解决方案13】:

                    当使用 Ruby 作为 shell 脚本时,上面的答案很有趣并且很有帮助。对我来说,我不使用 Ruby 作为我的日常语言,我更喜欢仅使用 ruby​​ 作为流控制,并且仍然使用 bash 来完成任务。

                    一些辅助函数可用于测试执行结果

                    #!/usr/bin/env ruby
                    module ShellHelper
                      def test(command)
                        `#{command} 2> /dev/null`
                        $?.success?
                      end
                    
                      def execute(command, raise_on_error = true)
                        result = `#{command}`
                        raise "execute command failed\n" if (not $?.success?) and raise_on_error
                        return $?.success?
                      end
                    
                      def print_exit(message)
                        print "#{message}\n"
                        exit
                      end
                    
                      module_function :execute, :print_exit, :test
                    end
                    

                    有了 helper,ruby 脚本可以和 bash 一样:

                    #!/usr/bin/env ruby
                    require './shell_helper'
                    include ShellHelper
                    
                    print_exit "config already exists" if test "ls config"
                    
                    things.each do |thing|
                      next if not test "ls #{thing}/config"
                      execute "cp -fr #{thing}/config_template config/#{thing}"
                    end
                    

                    【讨论】: