1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
#
# shellwords.rb: Manipulates strings a la UNIX Bourne shell
#
#
# This module manipulates strings according to the word parsing rules
# of the UNIX Bourne shell.
#
# The shellwords() function was originally a port of shellwords.pl,
# but modified to conform to POSIX / SUSv3 (IEEE Std 1003.1-2001).
#
# Authors:
# - Wakou Aoyama
# - Akinori MUSHA <knu@iDaemons.org>
#
# Contact:
# - Akinori MUSHA <knu@iDaemons.org> (current maintainer)
#
module Shellwords
#
# Splits a string into an array of tokens in the same way the UNIX
# Bourne shell does.
#
# argv = Shellwords.split('here are "two words"')
# argv #=> ["here", "are", "two words"]
#
# +String#shellsplit+ is a shorthand for this function.
#
# argv = 'here are "two words"'.shellsplit
# argv #=> ["here", "are", "two words"]
#
def shellsplit(line)
words = []
field = ''
line.scan(/\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m) do
|word, sq, dq, esc, garbage, sep|
raise ArgumentError, "Unmatched double quote: #{line.inspect}" if garbage
field << (word || sq || (dq || esc).gsub(/\\(?=.)/, ''))
if sep
words << field
field = ''
end
end
words
end
alias shellwords shellsplit
module_function :shellsplit, :shellwords
class << self
alias split shellsplit
end
#
# Escapes a string so that it can be safely used in a Bourne shell
# command line.
#
# Note that a resulted string should be used unquoted and is not
# intended for use in double quotes nor in single quotes.
#
# open("| grep #{Shellwords.escape(pattern)} file") { |pipe|
# # ...
# }
#
# +String#shellescape+ is a shorthand for this function.
#
# open("| grep #{pattern.shellescape} file") { |pipe|
# # ...
# }
#
def shellescape(str)
# An empty argument will be skipped, so return empty quotes.
return "''" if str.empty?
str = str.dup
# Process as a single byte sequence because not all shell
# implementations are multibyte aware.
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
# A LF cannot be escaped with a backslash because a backslash + LF
# combo is regarded as line continuation and simply ignored.
str.gsub!(/\n/, "'\n'")
return str
end
module_function :shellescape
class << self
alias escape shellescape
end
#
# Builds a command line string from an argument list +array+ joining
# all elements escaped for Bourne shell and separated by a space.
#
# open('|' + Shellwords.join(['grep', pattern, *files])) { |pipe|
# # ...
# }
#
# +Array#shelljoin+ is a shorthand for this function.
#
# open('|' + ['grep', pattern, *files].shelljoin) { |pipe|
# # ...
# }
#
def shelljoin(array)
array.map { |arg| shellescape(arg) }.join(' ')
end
module_function :shelljoin
class << self
alias join shelljoin
end
end
class String
#
# call-seq:
# str.shellsplit => array
#
# Splits +str+ into an array of tokens in the same way the UNIX
# Bourne shell does. See +Shellwords::shellsplit+ for details.
#
def shellsplit
Shellwords.split(self)
end
#
# call-seq:
# str.shellescape => string
#
# Escapes +str+ so that it can be safely used in a Bourne shell
# command line. See +Shellwords::shellescape+ for details.
#
def shellescape
Shellwords.escape(self)
end
end
class Array
#
# call-seq:
# array.shelljoin => string
#
# Builds a command line string from an argument list +array+ joining
# all elements escaped for Bourne shell and separated by a space.
# See +Shellwords::shelljoin+ for details.
#
def shelljoin
Shellwords.join(self)
end
end
|