How do I manipulate filenames in bash? - bash

I have a bunch of images that I need to rename, so I can use them and I was wondering how to do this.
The way they need to be is that first 5 will be kept and then for the 6th I would write a number from 1-3. I only know that the first 5 are static; on pics belonging to same "family" and can be used for comparison and the 6th char is not known.
Example:
12345random.jpg
12345randomer.jpg
0987654more_random.jpg
09876awesome.jpg
09876awesomer.jpg
09876awesomest.jpg
09876soawesomegalaxiesexplode.jpg
would become.
12345.jpg
123452.jpg
09876.jpg
098761.jpg
098762.jpg
It would be cool if it would only handle the loop so that 3 pics could be only renamed and rest skipped.
I found some stuff on removing letters to certain point, but nothing that use, since I am quite poor at bash scripting.
Here is my approach, but it kind of sucks, since I tried modifying scripts I found, but the idea is there
//I could not figure how to remove the chars after 5th not the other way around
for file in .....*; do echo mv $file `echo $file | cut -c6-`; done
done
//problem also is that once the names conflict it produces only 1 file named 12345.jpg 2nd one will not be created
//do not know how to read file names to array
name=somefile
if [[ -e $name.jpg]] ; then
i=0
while [[ -e $name-$i.jpg]] ; do
let i++
done
name=$name-$i
fi
touch $name.jpg

You can have:
new_file=${file%%[^0-9]*.jpg}.jpg
As a concept you can have this to rename files:
for file in *.jpg; do
[[ $file == [0-9]*[^0-9]*.jpg ]] || continue ## Just a simple check.
new_file=${file%%[^0-9]*.jpg}.jpg
[[ -e $new_file ]] || continue ## Do not overwrite. Delete line if not wanted.
echo "Renaming $file to $new_file." ## Optional message.
mv -- "$file" "$new_file" || echo "Failed to rename $file to $new_file."
done
If you're going to process files that also contain directory names, you'll need some more changes:
for file in /path/to/other/dirs/*.jpg *.jpg; do
base=${file##*/}
[[ $base == [0-9]*[^0-9]*.jpg ]] || continue
if [[ $file == */* ]]; then
new_file=${file%/*}/${base%%[^0-9]*.jpg}.jpg
else
new_file=${file%%[^0-9]*.jpg}.jpg
fi
[[ -e $new_file ]] || continue
echo "Renaming $file to $new_file."
mv -- "$file" "$new_file"
done

you can also try the following code
but be careful all the files should be in .jpg format and pass the name of folder as an argument
#!/bin/bash
a=`ls $1`
for b in $a
do
echo $b
if (( i<4 ))
then
c=`echo $b | cut -c1-5`
let i=i+1
c="$c$i.jpg"
echo $c
else
c=`echo $b | cut -c1-5`
c="$c.jpg"
break
fi
mv $1$b $1$c
done

Related

Loop through first 12 files in directory and break out if file is found

In Bash, what is the best way to loop through the first 12 folders in a directory in search of a file, and if the file is found then exit out of both loops.
This is my attempt so far. It doesn't:
limit the search scope to the first 12 folders
break out of the nested for loop when a file is found
How do I fix this?
#!/bin/bash
value="test.txt"
file_found = false
cd /backup/logs || exit 1
ls -1tr | head -n -12 | while read -r folder; do
cd /backup/logs/${folder}
ls -1tr | while read -r file; do
if [[ ${file} == ${value} ]]; then
file_found=true
echo "file found in ${folder}"
break
fi
done
if [[ ${file_found} == true ]]; then
break
fi
done
Use head -n 12 to list the first 12 lines of the output, not -n -12: it lists all the files but the last 12.
Use break 2 to break from nested loops of depth 2.
Use ls -d */ to only list directories (i.e. don't show files, don't show contents of the directories).
Use a for loop, not a while loop. You don't need a second loop; you can test directly if a file named "$value" exists.
i=0
for folder in /backup/logs/*
if [ "$i" = 12 ]; then break; fi
cd /backup/logs/"$folder"
if [ -f "$value" ]; then
echo "file found in $folder"
break
fi
i=$((i+1))
done

Using a shell script involving echo to add student header to C++ files: Replaces \n with actual newline, any way to change behavior?

I'm taking a C++ course that requires a student header at the top of every submitted file. Typing it or yank/paste-ing is so tedious, I've been working on a script to just add it for me. I've got something that works for me so far, but I just noticed that every \n in any string in the files that it operates on are replaced with an actual newline. I'm guessing this is either a result of the use of cat or echo in the script, and I'm trying to figure out how to avoid that.
The manpage for echo says that the default behavior is to ignore backslash escapes, but I'm not entirely sure how that relates to what I'm trying to do.
My script is:
#!/bin/bash
NAME="Joseph Morgan"
CLASS="CISP 400 MoWe 3:00pm"
ASSIGNMENT=$1
DATE=$(date -I)
if [[ $# -eq 0 ]] ; then
echo 'Argument required:'
echo 'Usage: SOME_PROJECT_NAME [Adds project name to header] | d [Deletes header]'
exit 0
fi
if [ $1 == "d" ] ; then
echo 'Deleting header - Be careful! If no header is present, the first five lines of your files will be deleted'
read -p "Are you sure? (y/n)" -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
for f in ./*.cpp ;
do
echo "$(tail -n +6 $f)" > $f
done
for f in ./*.h ;
do
echo "$(tail -n +6 $f)" > $f
done
fi
exit 0
fi
for f in ./*.cpp ;
do
echo -e "// $NAME\n// $CLASS\n// $ASSIGNMENT\n// $DATE\n\n$(cat $f)" > $f
done
for f in ./*.h ;
do
echo -e "// $NAME\n// $CLASS\n// $ASSIGNMENT\n// $DATE\n\n$(cat $f)" > $f
done
If there is another way entirely to accomplish this, feel free to suggest it. I'm much more interested in learning here, the script was just for fun/education so it's not incredibly important.
Thanks!
The problem is with -e parameter given to echo(1) command, which makes it interpret \n as a new line. Delete that -e and it will work. I mean, you need that -e, when you write your own headers, but you should move the $(cat $f) outside of the "echo -e". For example, in two lines:
echo -e "// $NAME\n// $CLASS\n// $ASSIGNMENT\n// $DATE\n\n" > $f
echo "$(cat $f)" >> $f # notice the double angle >>
BUT BEWARE, that would erase your file before reading it. Even here there is a problem:
echo "$(tail -n +6 $f)" > $f
because it could erase (empty) the "$f" file before reading it. You could do instead:
newcontent=$(tail -n +6 $f)
echo "$newcontent" > $f
So, to add your headers, use two distinct echoes, but read the file before writing in it:
newcontent="$(cat $f)"
echo -e "// $NAME\n// $CLASS\n// $ASSIGNMENT\n// $DATE\n\n" > $f
echo "$newcontent" >> $f # notice the double angle >>
I hope it helps.

Combing several subqueries in IF statement

Current directory contains new logs that keep coming.
/tmp/logstash/ dir contains logs to which I will be comparing new ones
Conditions:
If the new log has the same name and the size that already exists in /tmp/logstash, I should get 'identical file already exists' msg.
Otherwise the script will move the new log to /tmp/logstash/.
Note, that if name is same but size is different, the script should still move new file to tmp/logstash/
My script is as follows and it's not working properly with with combining 'then && if', can you please help to fix it?
for file in *.log; do
new_filesize=$(du -b "$file" | cut -f 1)
if [[ -e /tmp/logstash/"$file" ]]
then
old_filesize=$(du -b /tmp/logstash/"$file" | cut -f 1) &&
if [[ "$new_filesize"="$old_filesize" ]]; then
echo "The file already exists"
fi
else mv $file /tmp/logstash
fi
done
You need spaces around the = in the conditional expression:
if [[ $new_filesize = $old_filesize ]]; then
Without the spaces, you're just testing whether the concatenated string "$new_filesize"="$old_filesize" is non-empty.
Test for Existence, if so Test Filesize, otherwise Copy
Per your request in the comments. The following tests whether old_file exists. If it does, it then checks whether the sizes between new_file and old_file differ. If they differ, then it moves new_file to /tmp/logstash/ replacing old_file. If old_file exists and the files sizes are equal, then it will echo "The file already exists". In the event old_file doesn't exist, then is simply copies new_file to /tmp/logstash/.
for file in *.log; do
if [ -e /tmp/logstash/"$file" ]; then
if [ $(stat %s "$file") -ne $(stat %s /tmp/logstash/"$file") ]
mv -f "$file" /tmp/logstash
else
echo "The file already exists"
fi
else
cp "$file" /tmp/logstash/"$file"
fi
done
Note: Remember quote your variables.
With Variables new_filesize and old_filesize
for file in *.log; do
new_filesize=$(stat %s "$file")
if [ -e /tmp/logstash/"$file" ]; then
old_filesize=$(stat %s /tmp/logstash/"$file")
if [ $new_filesize -ne $old_filesize ]
mv -f "$file" /tmp/logstash
else
echo "The file already exists"
fi
else
cp "$file" /tmp/logstash/"$file"
fi
done
Note: mv -f was added to all cases where old_file exists to prevent move failure due to existing file.

Filename prefix test always failing in bash

i have a question about using shell to read files. That is to say, i have a folder like this:
folder
new_file.log (this is a file)
new_file2.log (this is a file)
new_file3.log (this is a file)
new (this is a subfolder)
new_file_from_subfolder.log
new_file2_from_subfolder.log
new_file3_from_subfolder.log
what i want is to read all the content from (direct) files, not files from the subfolder. In the above case, i need new_file.log to new_file3.log.
I know there is a simple way:
$ cat new*.log
but i also like to write a bash script:
for file in $(ls -a)
do
if [[ "$file" != "." && "$file" != ".." ]]; then
if [[ -f "$file" && "$file" == "^new" ]]; then **here is the problem**
[do something]
fi
fi
done
my problem is labeled as above. the bash code seems doesnot like
"$file" == ^new
if i run the bash, it basically does nothing, which means that files fail to meet the condition.
anything wrong?
[[ $foo = $bar ]] is a glob expression, not a regex; ^ has no special meaning there.
You probably want either the glob expression
[[ $file = new* ]]
or the regex
[[ $file =~ ^new ]]
Of course, in a real-world scenario, you'd just iterate only over the names that match your prefix:
for file in new*; do
: something with "$file"
done
...or, recursively (using FD 3 so you can still interact with the user in this code):
while IFS= read -u 3 -r -d '' file; do
: something with "$file"
done 3< <(find . -type f -name 'new*' -print0)
You're headed down the wrong track. Here's how to iterate over all regular files starting with new:
for file in new*
do
if [[ -f $file ]]
then
dosomething "$file"
fi
done

Check if each file in a list (which is in a file) exists in bash

I have a text file (ListOfAllFiles.txt) that has a list of 500 files some of which exist and some don't.
I'd like to make two texts files that indicate which files exist and which don't.
This is my code thus far:
#!/bin/bash
for f in $(cat /path/to/ListOfAllFiles.txt)
do
if [[ -f $f ]]; then
echo $f > /path/to/FilesFound.txt
else
echo $f > /path/to/FilesNOTFound.txt
fi
done
What am I doing wrong??
Your biggest problem is that each pass through the loop will overwrite either /path/to/FilesFound.txt or /path/to/FilesNOTFound.txt; instead of using >, you should be using >>. Fixing that, and making other improvements for robustness, we get:
#!/bin/bash
echo -n > /path/to/FilesFound.txt # reset to empty file
echo -n > /path/to/FilesNOTFound.txt # reset to empty file
while IFS= read -r f ; do
if [[ -f "$f" ]]; then
echo "$f" >> /path/to/FilesFound.txt
else
echo "$f" >> /path/to/FilesNOTFound.txt
fi
done < /path/to/ListOfAllFiles.txt

Resources