# Fixing Archive Annoyances with Ruby

I have recently become annoyed enough with something about the linux command line experience to take action.

I welcome all comments, but specific things I'd like input on are:

• Is there something already available that does this?
• How would you do the same thing in Perl or Python?
• How would you go about reducing code duplication here? (The file-types hash looks similar enough in both places that it feels like I should be able to abstract it)
• In unpack, is there a better way of determining file-type?
• Can you think of any situations that would cause pack to blow up (and any ways of solving those situations)? Just failing to do anything wouldn't be too bad, but (for example) compressing random other files is something I'd like to avoid.

Take 4:

unpack:

#!/usr/bin/ruby

archive_types = {
"tar"     => ["tar", "-xvf"],
"tar.gz"  => ["tar", "-zxvf"],
"tgz"     => ["tar", "-zxvf"],
"tar.bz2" => ["tar", "-jxvf"],
"rar"     => ["unrar", "x"],
"zip"     => ["unzip"]
}

ARGV.each do |target|
file_type = target.match(/\.([^\W0-9]{3}?(\.\w+)?)\$/)[1]
if archive_types[file_type]
args = archive_types[file_type].push target
system(*args)
else
puts "Dunno how to deal with '#{file_type}' files"
end
end


pack (with added --exclude options for my ease of use):

#!/usr/bin/ruby

require 'optparse'
require 'pp'
require 'fileutils'

archive_types = {
"tar"     => ["tar", "-cvf"],
"tar.gz"  => ["tar", "-zcvf"],
"tgz"     => ["tar", "-zcvf"],
"tar.bz2" => ["tar", "-jcvf"],
"zip"     => ["zip"]
}

########## parsing inputs
options = { :type => "tar", :excluded => [".git", ".gitignore", "*~"] }
optparse = OptionParser.new do|opts|
opts.on("-e", "--exclude a,b,c", Array,
"Specify things to ignore. Defaults to [#{options[:excluded].join ", "}]") do |e|
options[:excluded] = e
end
opts.on("-t", "--type FILE-TYPE",
"Specify archive type to make. Defaults to '#{options[:type]}'. Supported types: #{archive_types.keys.join ", "}") do |t|
options[:type] = t
end
end
optparse.parse!
##########

ARGV.each do |target|
if not archive_types[options[:type]]
puts "Supported types are #{archive_types.keys.join ", "}"
exit
elsif options[:type] == "zip"
exclude = options[:excluded].map{|d| ["-x", d]}.flatten
else
exclude = options[:excluded].map{|d| ["--exclude", d]}.flatten
end
args = archive_types[options[:type]] +
[target + "." + options[:type], target] +
exclude
system(*args)
end


Both your pack and unpack scripts will fail if any of the targets contain spaces (or other shell meta-characters) in their name. Generally it's almost always a bad idea to insert arguments into commands using string interpolation.

I assume you're using backticks rather than system because you don't want the output of the invoked commands to go to the screen. In that case you can use IO.popen, which since ruby 1.9 can take an array as its first argument.

As a trivial sidenote: There doesn't seem to be any point in passing the -v flag to tar if you're not going to print its output, so I'd remove it.

• Nope, just being an idiot. Now that I've had time to play, new versions using system instead of backticks. – Inaimathi Sep 21 '11 at 18:12

"Take 2" looks much nicer than the initial version. One bug seems to be "unrar" => "unrar", which should be "rar" => "unrar".

• Yup, caught that earlier; fixed above. – Inaimathi Sep 4 '11 at 3:16

I find take two (for unpack) to be harder to read, actually. It's less extensible, for sure, but for a shell script, it's nice to be very explicit about what you're doing.