BASH :: find file in archive from command line - bash

i know how to find file with find
# find /root/directory/to/search -name 'filename.*'
but, how to look also into archives, as file can be ziped inside...
thanx

I defined a function (zsh, minor changes -> BaSh)
## preview archives before extraction
# Usage: show-archive <archive>
# Description: view archive without unpack
show-archive() {
if [[ -f $1 ]]
then
case $1 in
*.tar.gz) gunzip -c $1 | tar -tf - -- ;;
*.tar) tar -tf $1 ;;
*.tgz) tar -ztf $1 ;;
*.zip) unzip -l $1 ;;
*.bz2) bzless $1 ;;
*) echo "'$1' Error. Please go away" ;;
esac
else
echo "'$1' is not a valid archive"
fi
}
You can
find /directory -name '*.tgz' -exec show-archive {} \| grep filename \;

find /directory -name '*.tgz' -exec tar ztf {} \| grep filename \;
or something like that... But I don't think there's an 'easy' solution.

If your archive is some sort of zipped tarball, you can use the feature of tar that searches for a particular file and prints only that filename. If your tar supports wildcards, you can use those too. For example, on my system:
tar tf sprt12823.logs.tar --wildcards *tomcat*
prints:
tomcat.log.20090105
although there are many more files in the tarball, but only one matching the pattern "*tomcat*". This way you don't have to use grep.
You can combine this with find and gunzip or whatever other zipping utility you've used.

Related

delete slash from variable

With help, i have this script but i am not sure how i get rid of "./" from the variable so i can zip the folder. Please can you advise me.
thanks
Nick
#/bin/sh
BASEDIR=/tmp/
cd $BASEDIR
find . -type d | sort > newfiles.txt
DIFF=$(comm -13 oldfiles.txt newfiles.txt)
echo $DIFF
if [ "$DIFF" != "" ]
then
#echo "A new dir is found"
tar -czvf "$DIFF".tar.gz --verbose
fi
mv newfiles.txt oldfiles.txt
failed output:
+ tar -czvf ./file2.tar.gz --verbose
tar: Cowardly refusing to create an empty archive
Try `tar --help' or `tar --usage' for more information.
The error stems from this command:
tar -czvf "$DIFF".tar.gz --verbose
You specify the archive name, but not the files/folders to be added to said archive.
You would need it to change to:
tar -czvf "$DIFF".tar.gz "$DIFF"
Note that you don't need --verbose since you already specified -v with -czvf.
However, your code won't work like intended if DIFF contains multiple items (i.e. lines). You probably want something like this:
#/bin/sh
BASEDIR="/tmp/"
cd "$BASEDIR"
find . -type d | sort > newfiles.txt
DIFF=$(comm -13 oldfiles.txt newfiles.txt)
echo "$DIFF"
IFS=$'\n'
for item in $DIFF
do
#echo "A new dir is found"
tar -czvf "$item.tar.gz" "$item"
done
mv newfiles.txt oldfiles.txt
That being said, if you want to remove ./ from find output, you can use:
find . -type d -printf "%P\n" | sort > newfiles.txt
but you don't actually have to do this for your script to work.

How to decompress multiple nested archives of different formats?

I have hundreds of .zip and .tar archives nested in each other with the unknown depth and I need to decompress all of them to get to the last one, how can I achieve that?
I have the part for the zip files:
while 'true'
do
find . '(' -iname '*.zip' ')' -exec sh -c 'unzip -o -d "${0%.*}" "$0"' '{}' ';'
done
but once it stumbles upon the .tar file it expectedly does nothing. I'm running the script on mac.
The structure is just an archive in an archive, the extensions are not in any particular order, like:
a.zip/b.zip/c.tar/d.tar/e.zip/f.tar...
and so on
You can use an existing command like 7z x to extract either archive type or build your own using case "$file" in; *.zip) unzip ...;; *.tar) ... and so on.
The following script unpacks nested archives as long as the unpacked content is exactly one .tar or .zip archive. It stops when multiple archives, multiple files, or even directory containing just one .zip, were unpacked at once.
#! /usr/bin/env bash
# this function can be replaced by `7z x "$1"`
# if 7zip is installed (package managers often call it p7zip)
extract() {
case "$1" in
*.zip) unzip "$1" ;;
*.tar) tar -xf "$1" ;;
*) echo "Unknown archive type: $1"; exit 1 ;;
esac
}
isOne() {
[ $# = 1 ]
}
mkdir out tmp
ln {,out/}yourOutermostArchive.zip # <-- Adapt this line
cd out
shopt -s nullglob
while isOne * && isOne *.{zip,tar}
do
a=(*)
mv "$a" ../tmp/
extract "../tmp/$a"
rm "../tmp/$a"
done
rm -r ../tmp
cd ..

How to untar every type of tar file in a directory with bash script?

I'm a beginner in writing bash scripts for automating tasks, and I'm trying to untar all the tar files in one directory (there are way too many to do it by hand) for a bunch of source code files. They're all of the type *.tar.gz, *.tar.xz, or *.tar.bz2.
This is for a Linux from Scratch LFS installation I'm doing (I'm a first timer), and I'm not sure how else to automate this task other than using a bash script. The code for my little script to do this is down below.
#!/bin/bash
for afile in 'ls -1'; do
if [ 'afile | grep \"\.tar\.gz\"' ];
then
tar -xzf afile
elif [ 'afile | grep \"\.tar\.xz\"' ]
then
tar -xJf afile
elif [ 'afile | grep \"\.tar\.xz\"' ]
then
tar -xjf afile
else
echo "Something is wrong with the program"
fi
done;
I expected it to untar everything in the directory and create separate directories, but instead it exited with this error:
tar (child): afile: Cannot open: No such file or directory
tar (child): Error is not recoverable: exiting now
tar: Child returned status 2
tar: Error is not recoverable: exiting now
Apparently it thinks afile is the actual file, but I don't know how to change afile to be each file that is going through my for construct. How would I write a script for this, especially since there are different types of files?
To get your script to work with minimal changes, use $afile whenever you want the variable's value. The dollar sign makes a variable reference; otherwise you just get the literal string 'afile'. Also get rid of the square brackets and instead echo the variable to grep.
for afile in `ls -1`; do
if echo "$afile" | grep '\.tar\.gz'
then
tar -xzf "$afile"
elif echo $afile | grep '\.tar\.xz'
then
tar -xJf "$afile"
elif echo "$afile" | grep '\.tar\.bz2'
then
tar -xjf "$afile"
else
echo "Something is wrong with the program"
fi
done
Since you're a bash beginner, let's look at various other ways you could write the script. I'd make a couple of improvements. For one, you shouldn't loop over ls. You can get the same thing by looping over *. Second, grep is a heavyweight tool. You can do some simple string comparisons with built-in shell constructs like [[ and ==.
for afile in *; do
if [[ "$afile" == *.tar.gz ]]; then
tar -xzf "$afile"
elif [[ "$afile" == *.tar.xz ]]; then
tar -xJf "$afile"
elif [[ "$afile" == *.tar.bz2 ]]; then
tar -xjf "$afile"
else
echo "Something is wrong with the program"
fi
done
Actually, this would be even nicer with a case statement. Let's try that. Also let's echo the error message to stderr with >&2. That's always a good idea.
for afile in *; do
case "$afile" in
*.tar.gz) tar -xzf "$afile";;
*.tar.xz) tar -xJf "$afile";;
*.tar.bz2) tar -xjf "$afile";;
*) echo "Something is wrong with the program" >&2
esac
done
We could even get rid of the error message if we just list the three types of files we want to loop over. Then there's no way to hit the else case.
for afile in *.tar.{gz,xz,bz2}; do
case "$afile" in
*.tar.gz) tar -xzf "$afile";;
*.tar.xz) tar -xJf "$afile";;
*.tar.bz2) tar -xjf "$afile";;
esac
done
Or a completely different way to do it: use find to find all the files and its -exec action to call a command for each file it finds. Here {} is a placeholder for the files it finds.
find . -name '*.tar.gz' -exec tar -xzf {} \;
find . -name '*.tar.xz' -exec tar -xJf {} \;
find . -name '*.tar.bz2' -exec tar -xjf {} \;

How to untar specific files from a number of tar files and zip them?

The requirement is to extract all the *.properties files from multiple tars and put them into a zip.
I tried this:
find . -iwholename "*/ext*/*.tar.gz"|xargs -n 1 tar --wildcards '*.properties' -xvzf | zip -# tar-properties.zip
This is creating a zip with the .properties files in all the tars.
But the issue is the tars are structured as in each tar contains a properties folder which contains the files. The above command is creating a zip with a single properties folder which contains all the files .
Is there a way to put these in the zip with a folder structure like {name of the tar}/properties/*.properties ?
You could use this script. My solution uses --transform as well. Please check first if your tar command supports it with tar --help 2>&1 | grep -Fe --transform.
#!/bin/bash
[ -n "$BASH_VERSION" ] || {
echo "You need bash to run this script." >&2
exit 1
}
TEMPDIR=/tmp/properties-files
OUTPUTFILE=$PWD/tar-properties.zip ## Must be an absolute path.
IFS=
if [[ ! -d $TEMPDIR ]]; then
mkdir -p "$TEMPDIR" || {
echo "Unable to create temporary directory $TEMPDIR." >&2
exit 1
}
fi
NAMES=()
while read -r FILE; do
NAMEOFTAR=${FILE##*/} ## Remove dir part.
NAMEOFTAR=${NAMEOFTAR%.tar.gz} to remove extension ## Remove .tar.gz.
echo "Extracting $FILE."
tar --wildcards '*.properties' -xvzf "$FILE" -C "$TEMPDIR" --transform "s#.*/#${NAMEOFTAR//#/\\#}/properties/#" || {
echo "An error occurred extracting to $TEMPDIR." >&2
exit 1
}
NAMES+=("$NAMEOFTAR")
done < <(exec find . -type f -iwholename '*/ext*/*.tar.gz')
(
cd "$TEMPDIR" >/dev/null || {
echo "Unable to change directory to $TEMPDIR."
exit 1
}
zip -a "$OUTPUTFILE" "${NAMES[#]}"
)
Save it to a script then run it on the directory where those files are to be searched with
bash /path/to/script.sh`
You can probably do the trick with tar option --transform, --xform. This option permits to manipulate path thanks to a sed expression.
find . -iwholename "*/ext*/*.tar.gz"|xargs -n 1 tar --wildcards '*.properties' -xvzf --xform 's#.*/#name_of_the_tar/properties/#' | zip -# tar-properties.zip

Rename file names in current directory and all subdirectories

I have 4 files with the following names in different directories and subdirectories
tag0.txt, tag1.txt, tag2.txt and tag3.txt
and wish to rename them as tag0a.txt, tag1a.txt ,tag2a.txt and tag3a.txt in all directories and subdirectories.
Could anyone help me out using a shell script?
Cheers
$ shopt -s globstar
$ rename -n 's/\.txt$/a\.txt/' **/*.txt
foo/bar/tag2.txt renamed as foo/bar/tag2a.txt
foo/tag1.txt renamed as foo/tag1a.txt
tag0.txt renamed as tag0a.txt
Remove -n to rename after checking the result - It is the "dry run" option.
This can of course be done with find:
find . -name 'tag?.txt' -type f -exec bash -c 'mv "$1" ${1%.*}a.${1##*.}' -- {} \;
Here is a posix shell script (checked with dash):
visitDir() {
local file
for file in "$1"/*; do
if [ -d "$file" ]; then
visitDir "$file";
else
if [ -f "$file" ] && echo "$file"|grep -q '^.*/tag[0-3]\.txt$'; then
newfile=$(echo $file | sed 's/\.txt/a.txt/')
echo mv "$file" "$newfile"
fi
fi
done
}
visitDir .
If you can use bashisms, just replace the inner IF with:
if [[ -f "$file" && "$file" =~ ^.*/tag[0-3]\.txt$ ]]; then
echo mv "$file" "${file/.txt/a.txt}"
fi
First check that the result is what you expected, then possibly remove the "echo" in front of the mv command.
Using the Perl script version of rename that may be on your system:
find . -name 'tag?.txt' -exec rename 's/\.txt$/a$&/' {} \;
Using the binary executable version of rename:
find . -name 'tag?.txt' -exec rename .txt a.txt {} \;
which changes the first occurrence of ".txt". Since the file names are constrained by the -name argument, that won't be a problem.
Is this good enough?
jcomeau#intrepid:/tmp$ find . -name tag?.txt
./a/tag0.txt
./b/tagb.txt
./c/tag1.txt
./c/d/tag3.txt
jcomeau#intrepid:/tmp$ for txtfile in $(find . -name 'tag?.txt'); do \
mv $txtfile ${txtfile%%.txt}a.txt; done
jcomeau#intrepid:/tmp$ find . -name tag*.txt
./a/tag0a.txt
./b/tagba.txt
./c/d/tag3a.txt
./c/tag1a.txt
Don't actually put the backslash into the command, and if you do, expect a '>' prompt on the next line. I didn't put that into the output to avoid confusion, but I didn't want anybody to have to scroll either.

Resources