18

Is it possible to rename a directory inside of a tar archive? My use case is that I have an externally provided RPM spec file that assumes a tarball with a certain directory structure, and I have an externally provided tarball whose top-level directory name does not match what the spec file expects. I don't control either the script that generates the tarballs or the RPM spec file, so I can't make a change to either of those to match the other.

What I have been doing is untarring, changing the directory name, and then creating a new tarball, but I was wondering if there was an alternative to doing that.

1
  • 3
    Unfortunately I doubt it, but I'd be interested to see if anyone else has a way. I think your best bet is to write a script to untar, rename, and re-tar. – Kevin Dec 5 '11 at 14:30
12

It shouldn't be very difficult, at least for archives that are compatible with the old-style format where file names are stored in a fixed-size (100 bytes) field, but I don't know of any tool that can rename a file in place in a tar archive. Besides, with a compressed archive, you'd need to create a new file anyway.

It should be even easier, but I don't know of any existing tool that can filter an archive, renaming files as it goes. You can build one on top of tar libraries in scripting languages; for example, here's a proof-of-concept script to rename a directory in a tar archive using Perl with Archive::Tar. The archive is loaded entirely into memory; this is an intrinsic limitation of Archive::Tar.

#!/usr/bin/env perl
## Usage: tar-rename OLDPREFIX NEWPREFIX
use strict;
use warnings;
use Archive::Tar;
my ($from, $to) = @ARGV;
my $tar = Archive::Tar->new(\*STDIN);
foreach my $file ($tar->get_files()) {
    my $name = $file->name;
    $name =~ s~\A\Q$from\E($|/)~$to$1~;
    $file->rename($name) unless $name eq $file->name;
}
$tar->write(\*STDOUT);

GNU tar doesn't have the ability to rename members on the fly, but pax (POSIX's replacement for cpio and tar) does. However, you can't make pax both read and write from an archive. What you can do is expose the archive as a regular tree through AVFS, and create a new archive with pax. This retains file names (except as transformed), contents, times and modes but resets file ownership to you (unless executed as root).

mountavfs
cd "~/.avfs$PWD/old.tgz#"
pax -w -s '!bar!baz!' -s '!bar/!baz/' . | gzip >new.tgz
3
  • GNU Tar has had --transform since at least 2010 that does allow renaming files on the fly. For an example, see: scriptsandoneliners.blogspot.com/2019/11/… – A.Danischewski Nov 7 '19 at 21:46
  • @A.Danischewski --transform lets you rename files when you build or extract an archive. But how do you rename files in an archive without extracting it? (Extracting is not a good solution: you may not have enough disk space; you may not have the permissions to preserve ownership information; you may not have the ability to preserve timestamps exactly...) – Gilles 'SO- stop being evil' Nov 7 '19 at 21:47
  • Okay, yea I just saw your post state that "GNU tar doesn't the ability to rename members on the fly" - it does but only on the way in/out. If you want to change names within an existing archive you can use archivemount to mount it to a directory, change whatever names you want and unmount it. – A.Danischewski Nov 8 '19 at 0:17
4

Both sr_'s hack and Gilles' answer look very good, but if your problem is just the root directory name of the target tarball, while running rpmbuild, a different solution could be to re-define the %setup macro to do the needed dir renaming.

Something like (you'll have to adapt and refine this to your actual configuration, in particular replacing old-dir and desired-dir and using the right decompression tool) this in your ~/.rpmmacros:

%setup cd ../BUILD \
rm -rf cd-player \
bunzip2 -dc ../SOURCES/%{name}-%{version}.tar.bz2 | tar -xvvf - \
if [ $? -ne 0 ]; then \
  exit $? \
fi \
mv <old-dir> <desired-dir> \
cd <desired-dir> \
cd ../BUILD/cd-player \
chmod -R a+rX,g-w,o-w .

I wouldn't honestly do that if not in the most exotic situation, but yours could be the case :)

3

Just view this page but found the proper answer elsewhere:

http://www.rpm.org/max-rpm/s1-rpm-inside-macros.html

It says that you can pass -n to the %setup macro to tell rpmbuild the name of the top level folder within the tarball

2

Ugly hack, but maybe this helps you, fooling tar with symlinks:

$ mkdir a b
$ date >> b/foo
$ tar zcvf b-foo.tgz b/foo
$ rm -rf b
$ ln -s a b

$ tar zxvf b-foo.tgz                                                              
x b/foo: Cannot extract through symlink b
tar: Error exit delayed from previous errors.
$ tar zxvPf b-foo.tgz                                                             
x b/foo
$ ls a
foo

Thinking about it, it probably doesn't, as rpm won't let you mess with the arguments to tar, would it? (Edit: maybe some tricky tar wrapper script in $PATH could get you around this.)

0

This article was so close to what I needed but no cigar. The rename function inside of Archive::Tar was messing up my folders. It worked great for files but the folders were coming out without the trailing slash (/). Example:

Before

$VAR1 = [
          'old_root_folder/',
          'old_root_folder/.dockerignore',
          'old_root_folder/.github/',
.....

After

$VAR1 = [
          'newrootfolder',
          'newrootfolder/.dockerignore',
          'newrootfolder/.github/',
......

Notice the root folder no longer has the slash designation! Turns out that doesn't matter. The new archive extracts fine without the folders ending with a trailing slash

I ended up with the following Perl snippet:

sub renameRootFolderInTar
{
    my $file = shift;
    my $new_root_folder_name = shift;
    my $tar = Archive::Tar->new($file);
    my @files_in_archive = $tar->list_files;
    my $root_folder = @files_in_archive[0]; # whatever they named the root folder in the archive
    $root_folder =~ s/\/$//g;
    foreach(@files_in_archive)
    {
        my $this_archive_file = $_;
        my $dest = $this_archive_file;
        $dest =~ s/^$root_folder\/(.*)/$new_root_folder_name\/$1/g;
        $tar->rename($this_archive_file,$dest);
    }
    my $success = $tar->write( "renamed.tar.gz", COMPRESS_GZIP );
    if($success)
    {
        unlink $file;
        return "renamed.tar.gz";
    }
    else
    {
        print "Sorry, there was a problem when dealing with the raw archive $file:\n";
        print "Could not save renamed.tar.gz\n";
        exit;
    }
}

Admittedly this assumes that the first entry in the archive is a folder. But you get the idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.