Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby에서 명령줄 앱 작성

소소한 유틸리티 스크립트를 작성하는 것은 재미있지만, 때로는 정말 정직한 명령줄 응용 프로그램을 작성해야 할 필요가 있습니다. 인수를 취하고 입력, 출력, 오류 보고 등에 대한 유닉스 규칙과 잘 어울리는 것입니다.

다행히 Ruby는 명령줄 응용 프로그램에 필요한 모든 구성 요소를 상당히 쉽게 제공합니다. 이 게시물에서 저는 일반적인 "젬 Y로 X를 수행하는 방법" 접근 방식을 넘어 일류 명령줄 앱을 만들기 위해 함께 사용되는 모든 부분에 대한 광범위한 개요를 제공하고자 합니다.

입력:환경 변수

환경 변수는 일반적으로 잠시 동안 유지하려는 짧은 구성 값에 사용됩니다. API 키가 좋은 예입니다.

환경 변수 설정

사용자는 일반적으로 bash와 같은 셸을 통해 환경 변수를 설정합니다. 특정 세션에 대해 설정할 수 있습니다.

$ AWS_ACCESS_KEY_ID=FOO

현재 세션과 새 bash 세션에 대해 설정할 수 있습니다.

$ export AWS_ACCESS_KEY_ID=FOO

또는 단일 프로그램에 대해 설정할 수 있습니다.

$ env AWS_ACCESS_KEY_ID=FOO aws s3 mb s3://mybucket

환경 변수 읽기

사용자가 환경 변수를 어떻게 설정하든 관계없이 Ruby의 ENV를 통해 읽을 수 있습니다. 해시.

key_id = ENV["AWS_ACCESS_KEY_ID"]

환경 변수는 내부적으로 어떻게 작동합니까?

모든 프로세스에는 연관된 환경 변수 테이블이 있습니다. 자식 프로세스를 생성할 때 부모가 변경하려는 모든 변경 사항과 함께 해당 테이블의 복사본을 얻습니다.

진흙처럼 맑습니까?

더 읽기 쉬운 설명을 보려면 내 다른 블로그 게시물인 Rubyist's Guide to Environment Variables를 확인하세요.

명령줄 인수

명령줄 인수는 터미널에서 프로그램을 실행할 때 프로그램 이름 뒤에 넣는 모든 것입니다.

$ echo foo bar baz

위의 예에서 "foo", "bar" 및 "baz"는 모두 명령줄 인수입니다.

보다 현실적인 예는 다음과 같습니다.

$ honeybadger deploy -v --environment staging

그러나 여기에서도 명령줄 인수는 텍스트일 뿐입니다. --environment staging를 원하는 경우 의미 무엇이든 우리 스스로 해결해야 합니다.

명령줄 인수 보내기

다음과 같이 전송된 인수를 이미 보았습니다.

$ echo foo bar baz

하지만 Ruby는 인터프리터 언어이기 때문에 다음과 같은 내용을 접할 수도 있습니다.

$ ruby myprog.rb foo

OS에 관한 한 "ruby"라는 프로그램을 실행하고 두 개의 인수를 전달합니다.

운 좋게도 Ruby는 "foo" 인수가 Ruby 자체가 아니라 프로그램을 위한 것임을 알 만큼 똑똑합니다. 따라서 프로그램에는 "foo"라는 하나의 인수가 표시됩니다. .

명령줄 인수 읽기

명령줄 인수는 ARGV라는 배열에 저장됩니다. . 전역 변수이므로 프로그램의 어디에서나 액세스할 수 있습니다.

아래 예에서는 ARGV의 모든 내용을 인쇄하고 있습니다. . 이것을 자유롭게 복사하여 터미널에 붙여넣고 조금 가지고 놀아보세요.

$ ruby -e "puts ARGV.inspect" foo bar baz
["foo", "bar", "baz"]
<블록 인용>

ruby -e에 휩쓸리지 마십시오. 사업. 여기서는 프로그램과 실행 결과를 한 줄로 표시할 수 있기 때문에 여기에서만 사용하고 있습니다.

명령줄 인수 구문 분석

명령줄 인수는 텍스트일 뿐입니다. 텍스트가 의미가 되도록 하려면 무엇이든, 당신은 그것을 구문 분석해야 할 것입니다. 다행히도 구문 분석에 도움이 될 수 있는 몇 가지 좋은 라이브러리가 있습니다.

수년에 걸쳐 명령줄 인수에 대한 다소 표준적인 구문이 등장했습니다. 다음과 같이 보입니다.

$ program -a --option foo

이 스타일을 사용하면 -h와 같은 부울 플래그를 사용할 수 있습니다. 도움말을 표시합니다. 또한 값으로 옵션을 지정할 수 있습니다.

OptionParser 소개

OptionParser class는 Ruby의 표준 라이브러리의 일부입니다. 위의 스타일과 일치하는 옵션을 쉽게 구문 분석할 수 있는 방법을 제공합니다.

Hello라는 간단한 응용 프로그램을 만들어 보겠습니다. 명령줄 인수를 통해 이름을 지정할 수 있습니다.

require 'optparse'

# This will hold the options we parse
options = {}

OptionParser.new do |parser|

  # Whenever we see -n or --name, with an
  # argument, save the argument.
  parser.on("-n", "--name NAME", "The name of the person to greet.") do |v|
    options[:name] = v
  end
end.parse!

# Now we can use the options hash however we like.
puts "Hello #{ options[:name] }"  if options[:name]

그런 다음 실행하면 제대로 작동합니다.

$ ruby hello.rb --name Starr
Hello Starr

$ ruby hello.rb -n Starr
Hello Starr

"도움말" 화면 추가

도움말 기능을 추가하는 것도 간단합니다. 우리가 해야 할 일은 텍스트를 제공하고 -h에 대한 명령을 추가하는 것입니다. :

OptionParser.new do |parser|
  parser.banner = "Usage: hello.rb [options]"

  parser.on("-h", "--help", "Show this help message") do ||
    puts parser
  end

  ...
end.parse!

이제 도움을 요청할 수 있습니다.

$ ruby hello.rb -h
Usage: hello.rb [options]
    -h, --help                       Show this help message
    -n, --name NAME                  The name of the person to greet.

타입캐스팅 인수

모든 명령줄 인수는 문자열이지만 때때로 사용자가 숫자나 날짜를 제공하기를 원할 수 있습니다. 수동으로 변환하는 것은 지루할 수 있으므로 OptionParser 당신을 위해 그것을합니다.

아래 예에서 사용자가 프로그램에서 인사를 몇 번이나 해야 하는지 지정할 수 있는 "count" 옵션을 추가하겠습니다. OptionParser Integer로 형변환 , 그렇게 합니다.

OptionParser.new do |parser|

  ...

  # Note the `Integer` arg. That tells the parser to cast the value to an int.
  # I could have used `Float`, `Date`, or a number of other types.
  parser.on("-c", "--count COUNT", Integer, "Repeat the message COUNT times") do |v|
    options[:count] = v
  end

end.parse!

if options[:name]
  options.fetch(:count, 1).times do
    puts "Hello #{ options[:name] }"
  end
end

이제 프로그램을 실행하면 여러 번 인사를 받습니다.

$ ruby hello.rb -n Starr -c 5
Hello Starr
Hello Starr
Hello Starr
Hello Starr
Hello Starr

이름 지정 규칙

프로그래밍의 가장 어려운 측면 중 하나는 이름을 지정하는 것입니다. 명령줄 인수도 예외는 아닙니다. 여러분을 돕기 위해 일반적인 주장과 그 의미에 대한 짧은 표를 작성했습니다.

플래그 일반적인 의미
-a 모두, 추가
-d 디버그 모드 또는 디렉토리 지정
-e 실행 또는 편집
-f 파일을 지정하거나 작업을 강제 실행합니다.
-h 도움말
-m 메시지 지정
-o 출력 파일 또는 장치 지정
-q 저소음 모드
-v 상세 모드. 현재 버전 인쇄
-y 모든 프롬프트에 "예"라고 말하세요.

OptionParser의 대안

OptionParser 좋긴 한데 한계가 있습니다. 가장 분명한 것은 최근 몇 년 동안 인기를 얻은 명령 기반 구문을 지원하지 않는다는 것입니다.

$ myprog command subcommand -V

다행히도 옵션 구문 분석 라이브러리가 있습니다. 나는 당신이 좋아하는 것을 찾을 수 있을 것이라고 확신합니다. 다음은 흥미롭게 보이는 세 가지입니다.

  • GLI - "Git-like Interface" 명령줄 파서를 사용하면 git과 같이 여러 명령으로 단일 실행 파일인 응용 프로그램을 쉽게 만들 수 있습니다.
  • CRI - 중첩 명령을 지원하는 명령줄 도구를 빌드하기 위한 사용하기 쉬운 라이브러리입니다.
  • Methadone - Methadone은 DSL을 구문 분석하는 자체 옵션을 제공하지만 그 이상입니다. 테스트 스위트 및 로깅을 위한 디렉토리 구조를 설정합니다. CLI용 Rails와 같습니다.

STDIN으로 더 많은 양의 데이터 입력

명령줄 인수는 더 많은 양의 데이터를 입력하는 데 적합하지 않습니다. 이를 위해 IO 스트림을 사용하고 싶을 것입니다. 그리고 STDIN은 가장 편리한 IO 스트림 중 하나입니다.

대부분의 프로그램은 운영 체제에서 자동으로 STDIN을 할당합니다. OS가 앱 데이터를 보내는 데 사용하는 읽기 전용 파일과 같습니다. 사용자로부터 키보드 입력을 받는 데 사용할 수 있습니다. 그러나 더 중요한 것은 파이프를 통해 다른 프로그램에서 데이터를 수신하는 데 사용할 수 있다는 것입니다.

읽기 전용 파일을 사용하는 것처럼 STDIN을 사용합니다. 여기에서 일부 텍스트를 내 프로그램의 STDIN에 파이핑하고 있습니다. 그런 다음 read를 사용합니다. 가져오는 방법입니다.

$ echo "hello world" | ruby -e "puts STDIN.read.upcase"
HELLO WORLD

Ruby에서는 Enumerable을 사용할 수 있습니다. IO 개체의 기능. 그리고 STDIN도 예외는 아닙니다. 즉, 모든 종류의 트릭을 수행할 수 있습니다.

# Get the first 20 lines
STDIN.first(20)

# Convert to integers and reject odds
STDIN.map(&:to_i).reject(&:odd)

...etc

STDOUT으로 결과 출력

STDOUT은 운영 체제가 프로그램에 할당하는 쓰기 전용 IO 스트림입니다.

STDOUT에 작성하여 사용자의 터미널에 텍스트를 보낼 수 있습니다. 사용자는 출력을 파일로 리디렉션하거나 다른 프로그램으로 파이프할 수 있습니다.

다른 쓰기 전용 파일을 사용하는 것처럼 STDOUT을 사용합니다.

STDOUT.write("Hi!\n")
# hi

물론 putsprint 둘 다 STDOUT으로 출력됩니다.

STDERR에 상태 정보 보내기

STDERR은 또 다른 쓰기 전용 IO 스트림입니다. 일반 출력용이 아닙니다. 이는 특히 상태 메시지를 위한 것이므로 실제 출력.

STDERR은 일반적으로 사용자가 STDOUT을 파일이나 다른 프로그램으로 리디렉션하더라도 사용자 터미널에 표시됩니다.

아래 예에서는 curl을 사용하여 웹 페이지를 가져옵니다. 웹 페이지의 내용을 STDOUT으로 출력하고 진행 정보를 STDERR로 표시합니다.

$ curl "https://www.google.com/" > /tmp/g.html
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  151k    0  151k    0     0   277k      0 --:--:-- --:--:-- --:--:--  277k

자신의 프로그램에서 STDERR에 쓰는 것은 쉽습니다. IO 개체처럼 처리하십시오.

STDERR.write("blah\n")
STDERR.puts("blah")

예쁜 막대기로 때리기

지금까지 본 예제는 디자인 상을 받지 못할 것입니다. 하지만 명령줄 앱이 보기 흉하거나 비대화형이어야 할 이유가 없습니다.

앱을 강화하기로 결정했다면 무거운 작업을 수행할 수 있는 엄청난 보석이 있습니다. 제가 좋아하는 세 가지가 있습니다:

  • highline - Highline은 사용자 입력을 수집, 검증 및 입력하는 데 많은 노력을 기울이는 훌륭한 라이브러리입니다.
  • command_line_reporter - ASCII 진행 보고서를 쉽게 생성할 수 있습니다.
  • 페인트 - ANSI 색상 코드를 쉽게 추가하여 지루한 오래된 텍스트를 색칠할 수 있습니다.

전체 목록 보기

종료 상태

프로그램이 오류와 함께 종료되면 종료 코드를 통해 OS에 알려야 합니다. 이것이 다음과 같은 bash 코드가 작동하는 것을 가능하게 하는 것입니다:

$ prog1 && prog2

bash의 &&에 익숙하지 않은 경우 연산자는 단순히 "prog1이 성공적으로 종료된 경우에만 prog2를 실행합니다."를 의미합니다.

Ruby는 기본적으로 "성공" 코드로 종료되고 예외 시 "실패" 코드로 종료됩니다. 다른 모든 것은 구현하기 나름입니다. 다행히도 쉽습니다.

# Any nonzero argument to `exit` counts as failure
exit(1)

자세한 내용은 내 다른 게시물 How to exit Ruby program을 확인하세요.

프로세스 이름 설정

명령줄 프로그램이 잠시 동안 실행될 예정이라면 사람들이 시스템 프로세스를 나열할 때 프로그램이 무엇인지 아는 것이 중요합니다. 일반적으로 Ruby 프로그램 프로세스 이름은 프로그램을 실행하기 위해 입력한 모든 것으로 구성됩니다. 따라서 ruby myapp -v를 입력했다면 , 프로세스의 이름입니다.

프로그램에 인수가 많으면 읽을 수 없게 될 수 있습니다. 따라서 보다 친숙한 프로세스 이름을 설정할 수 있습니다. 다음과 같이 할 수 있습니다.

Process.setproctitle("My Awesome Command Line App")

기분이 좋다면 프로세스 제목을 사용하여 주어진 순간에 프로세스가 수행하는 작업에 대한 정보를 사용자에게 제공할 수 있습니다.

Process.setproctitle("Mail Sender: initializing")
init(...)
Process.setproctitle("Mail Sender: connecting")
connect(...)
Process.setproctitle("Mail Sender: sending")
send(...)

자세한 내용은 내 게시물을 확인하세요:top 및 ps로 표시된 Ruby 스크립트의 프로세스 이름을 변경하는 방법