Unix shell script to find a file and replace name by pattern - shell

I have a folder called /input/temp. Inside the folder I have lot of files. I need to find the file of pattern Article_????_test_?????????.txt and replace by format below.
Article_????_?????????.txt
Below is the code I tried and which doesn't work:
echo "Please provide the file name Corresponding to DC..."
read file
ls $HOME/*.txt | grep $file
if [ $? -eq 0 ]
find . $file '*_Test_*' -exec bash -c 'mv $0 ${0/_Test/ }' {} \;
if [ $? -eq 0 ]
find . $file -name "*.txt" -exec bash -c "mv {} \`echo {} | sed -e 's/[_TEST_]/_/g'\`" \;
then
I Got below error:
find: 0652-083 Cannot execute bash:: A file or directory in the path name does not exist.
find: 0652-083 Cannot execute bash:: A file or directory in the path name does not exist.
bash can't be executed on my platform.

Unless the file name is a regular expression you can use if [ -e "$file" ] instead of ls + grep + test.
When you do find . $file '*_Test_*' the last three parameters are actually taken as files or directories to search underneath! It will return
all files in the current directory,
all files in the directory $file or the path $file if it's not a directory, and
all files in any directories matching *_Test_* or their paths if they are not directories.
There's no need for bash - you can run mv directly in -exec. This is just extra complexity for no gain.
Use $(command) instead of command for much easier quote handling. Each $() has a separate quoting context, so you can do for example echo "$(command "$(c2 "argument with spaces")")".

According to the link here:
This should work
ls -1 Article_????test?????????.txt|awk '{old=$0;gsub(/test/,"_",$0);system("mv \""old"\" "$0)}'

Also try the 'rename' command.
rename 's/_test_/_/' *.txt
You can fine tune the regular expression...

Update from your code:
cd $HOME
find . -name '*_Test_*' |while read line
do
echo mv ${line) ${line/_Test/}
done
If you need search the pattern Article_????test?????????.txt, try this
cd $HOME
find . -name 'Article_????_test_?????????.txt' |while read line
do
echo mv ${line) ${line/_Test/}
done

Related

Bash script to scan sub-directories and copy contents of file to another file

I need to do a bash command that will look through every home directory on a system, and copy the contents of the .forward file to a single file, along with copying the name of the directory it just copied from. So for example the final file would be something like forwards.txt and listeings would be
/home/user1
user1#email.com
/home/user2
user2#email.com
I've used this to list them to screen.
find /home -name '*' | cat /home/*/.forward
and it will print out the forward in each file but I'm not getting it to prefix it with which home directory it came from. Would I need to use a loop to do this? I had this test loop,
#!/bin/bash
for i in /home/*
do
if [ -d $i ]
then
pwd >> /tmp/forwards.txt
cat /home/*/.forward >> /tmp/forwards.txt
fi
done
But it went through the four home directories on my test setup and the forwards.txt file had the following listed four times.
/tmp
user1#email.com
user2#email.com
user3#email.com
user3#email.com
Thanks.
There is corrected version of your script:
#!/bin/bash
for i in /home/*
do
if [ -f "$i/.forward" ]
then
echo "$i" >> /tmp/forwards.txt
cat "$i/.forward" >> /tmp/forwards.txt
fi
done
Some points:
we checks for presents of .forward file inside home directory instead of existence of home directory itself
on each iteration $i contains name of home directory (like /home/user1). So we use its value instead of output of pwd command which always returns current directory (it doesn't change in our case)
instead of /home/*/.forward we use "/home/$i/.forward" because * after substitution gives to us all directories, while we need only current
Another, shortest version of this script may looks like this:
find /home -type f -name '.forward' | while read F; do
dirname "$F" >>/tmp/forwards.txt
cat "$F" >>/tmp/forwards.txt
done
I would write
for fwd in /home/*/.forward; do
dirname "$fwd"
cat "$fwd"
done > forwards.txt
A one liner (corrected):
find /home -maxdepth 2 -name ".forward" -exec echo "{}" >> /tmp/forwards.txt \; -exec cat "{}" >> /tmp/forwards.txt \;
This will output:
/home/user1/.forward
a#a.a
b#b.b
/home/user2/.forward
a#b.c

Finding text within files with filenames

I have a number of clients running a piece of software within their public_html directory. The software includes a file named version.txt that contains the version number of their software (the number and nothing else).
I want to write a bash script that will look for a file named version.txt directly within every user's /home/xxx/public_html/ and output both the path to the file, and the contents of the file, i.e:
/home/matt/public_html/version.txt: 3.4.07
/home/john/public_html/version.txt: 3.4.01
/home/sam/public_html/version.txt: 3.4.03
So far all I have tried is:
#!/bin/bash
for file in 'locate "public_html/version.txt"'
do
echo "$file"
cat $file
done
But that does not work at all.
find /home -type f -path '*public_html/version.txt' -exec echo {} " " `cat {}` \;
Might work for you, but you can go without echo and cat ("tricking" grep):
find /home -type f -path '*public_html/version.txt' -exec grep -H "." {} \;
Or do it using find:
find /home -name "*/public_html/version.txt" -exec grep -H "" {} \;
for i in /home/*/public_html/version.txt; do
echo $i
cat $i
done
will find all the relevant files (using shell wildcarding), echo the filename out and cat out the file.
If you want a more concise output, you should investigate grep and replace the echo/cat with an appropriate regular expression e.g.
grep "[0-9]\.[0-9]" $i

How can I manipulate file names using bash and sed?

I am trying to loop through all the files in a directory.
I want to do some stuff on each file (convert it to xml, not included in example), then write the file to a new directory structure.
for file in `find /home/devel/stuff/static/ -iname "*.pdf"`;
do
echo $file;
sed -e 's/static/changethis/' $file > newfile +".xml";
echo $newfile;
done
I want the results to be:
$file => /home/devel/stuff/static/2002/hello.txt
$newfile => /home/devel/stuff/changethis/2002/hello.txt.xml
How do I have to change my sed line?
If you need to rename multiple files, I would suggest to use rename command:
# remove "-n" after you verify it is what you need
rename -n 's/hello/hi/g' $(find /home/devel/stuff/static/ -type f)
or, if you don't have rename try this:
find /home/devel/stuff/static/ -type f | while read FILE
do
# modify line below to do what you need, then remove leading "echo"
echo mv $FILE $(echo $FILE | sed 's/hello/hi/g')
done
Are you trying to change the filename? Then
for file in /home/devel/stuff/static/*/*.txt
do
echo "Moving $file"
mv "$file" "${file/static/changethis}.xml"
done
Please make sure /home/devel/stuff/static/*/*.txt is what you want before using the script.
First, you have to create the name of the new file based on the name of the initial file. The obvious solution is:
newfile=${file/static/changethis}.xml
Second you have to make sure that the new directory exists or create it if not:
mkdir -p $(dirname $newfile)
Then you can do something with your file:
doSomething < $file > $newfile
I wouldn't do the for loop because of the possibility of overloading your command line. Command lines have a limited length, and if you overload it, it'll simply drop off the excess without giving you any warning. It might work if your find returns 100 file. It might work if it returns 1000 files, but it might fail if your find returns 1000 files and you'll never know.
The best way to handle this is to pipe the find into a while read statement as glenn jackman.
The sed command only works on STDIN and on files, but not on file names, so if you want to munge your file name, you'll have to do something like this:
$newname="$(echo $oldname | sed 's/old/new/')"
to get the new name of the file. The $() construct executes the command and puts the results of the command on STDOUT.
So, your script will look something like this:
find /home/devel/stuff/static/ -name "*.pdf" | while read $file
do
echo $file;
newfile="$(echo $file | sed -e 's/static/changethis/')"
newfile="$newfile.xml"
echo $newfile;
done
Now, since you're renaming the file directory, you'll have to make sure the directory exists before you do your move or copy:
find /home/devel/stuff/static/ -name "*.pdf" | while read $file
do
echo $file;
newfile="$(echo $file | sed -e 's/static/changethis/')"
newfile="$newfile.xml"
echo $newfile;
#Check for directory and create it if it doesn't exist
$dirname=$(dirname "$newfile")
if [ ! -d "$dirname" ]
then
mkdir -p "$dirname"
fi
#Directory now exists, so you can do the move
mv "$file" "$newfile"
done
Note the quotation marks to handle the case there's a space in the file name.
By the way, instead of doing this:
if [ ! -d "$dirname" ]
then
mkdir -p "$dirname"
fi
You can do this:
[ -d "$dirname"] || mkdir -p "$dirname"
The || means to execute the following command only if the test isn't true. Thus, if [ -d "$dirname" ] is a false statement (the directory doesn't exist), you run mkdir.
It's a fairly common shortcut when you see shell scripts.
find ... | while read file; do
newfile=$(basename "$file").xml;
do something to "$file" > "$somedir/$newfile"
done
OUTPUT="$(pwd)";
for file in `find . -iname "*.pdf"`;
do
echo $file;
cp $file $file.xml
echo "file created in directory = {$OUTPUT}"
done
This will create a new file with name whatyourfilename.xml, for hello.pdf the new file created would be hello.pdf.xml, basically it creates a new file with .xml appended at the end.
Remember the above script finds files in the directory /home/devel/stuff/static/ whose file names match the matcher string of the find command (in this case *.pdf), and copies it to your present working directory.
The find command in this particular script only finds files with filenames ending with .pdf If you wanted to run this script for files with file names ending with .txt, then you need to change the find command to this find /home/devel/stuff/static/ -iname "*.txt",
Once I wanted to remove trailing -min from my files. i.e. wanted alg-min.jpg to turn into alg.jpg. so after some struggle, managed to figure something like this:
for f in *; do echo $f; mv $f $(echo $f | sed 's/-min//g');done;
Hope this helps someone willing to REMOVE or SUBTITUDE some part of their file names.

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.

Bash rename extension recursive

I know there are a lot of things like this around, but either they don't work recursively or they are huge.
This is what I got:
find . -name "*.so" -exec mv {} `echo {} | sed s/.so/.dylib/` \;
When I just run the find part it gives me a list of files. When I run the sed part it replaces any .so with .dylib. When I run them together they don't work.
I replaced mv with echo to see what happened:
./AI/Interfaces/C/0.1/libAIInterface.so ./AI/Interfaces/C/0.1/libAIInterface.so
Nothing is replaced at all!
What is wrong?
This will do everything correctly:
find -L . -type f -name "*.so" -print0 | while IFS= read -r -d '' FNAME; do
mv -- "$FNAME" "${FNAME%.so}.dylib"
done
By correctly, we mean:
1) It will rename just the file extension (due to use of ${FNAME%.so}.dylib). All the other solutions using ${X/.so/.dylib} are incorrect as they wrongly rename the first occurrence of .so in the filename (e.g. x.so.so is renamed to x.dylib.so, or worse, ./libraries/libTemp.so-1.9.3/libTemp.so is renamed to ./libraries/libTemp.dylib-1.9.3/libTemp.so - an error).
2) It will handle spaces and any other special characters in filenames (except double quotes).
3) It will not change directories or other special files.
4) It will follow symbolic links into subdirectories and links to target files and rename the target file, not the link itself (the default behaviour of find is to process the symbolic link itself, not the file pointed to by the link).
for X in `find . -name "*.so"`
do
mv $X ${X/.so/.dylib}
done
A bash script to rename file extensions generally
#/bin/bash
find -L . -type f -name '*.'$1 -print0 | while IFS= read -r -d '' file; do
echo "renaming $file to $(basename ${file%.$1}.$2)";
mv -- "$file" "${file%.$1}.$2";
done
Credits to aps2012.
Usage
Create a file e.g. called ext-rename (no extension, so you can run it like a command) in e.g. /usr/bin (make sure /usr/bin is added to your $PATH)
run ext-rename [ext1] [ext2] anywhere in terminal, where [ext1] is renaming from and [ext2] is renaming to. An example use would be: ext-rename so dylib, which will rename any file with extension .so to same name but with extension .dylib.
What is wrong is that
echo {} | sed s/.so/.dylib/
is only executed once, before the find is launched, sed is given {} on its input, which doesn't match /.so/ and is left unchanged, so your resulting command line is
find . -name "*.so" -exec mv {} {}
if you have Bash 4
#!/bin/bash
shopt -s globstar
shopt -s nullglob
for file in /path/**/*.so
do
echo mv "$file" "${file/%.so}.dylib"
done
He needs recursion:
#!/bin/bash
function walk_tree {
local directory="$1"
local i
for i in "$directory"/*;
do
if [ "$i" = . -o "$i" = .. ]; then
continue
elif [ -d "$i" ]; then
walk_tree "$i"
elif [ "${i##*.}" = "so" ]; then
echo mv $i ${i%.*}.dylib
else
continue
fi
done
}
walk_tree "."

Resources