I have an experiment with 60 plots. I took 5 photos per treatment with a go-pro, plus a 6th photo of the sky to mark the point where I moved to the next plot (Total 360 photos). How could I write a bash script to rename these files automatically; i.e
Change the set of files:
GOPRO0001.jpg, GOPRO0002.jpg, ... , ... , GOPRO0360.jpg
Into something like:
plot1_1.jpg, ... , plot1_6.jpg, ..., ..., plot60_1.jpg, ... , plot60_6.jpg
What's the most efficient way of doing this? My thinking is I need to have 2 levels of iteration, but I'm not sure how to do it..
You can try this BASH script:
#!/bin/bash
i=1;j=1
for file in GOPRO*.jpg; do
mv "$file" "plot${j}_${i}.jpg"
if [[ $i -eq 6 ]]; then
i=1
((j++))
else
((i++))
fi
done
If you want to verify what it would do before actually letting it take action, put an echo before the mv command.
Related
This problem is a homework, I know there would be much easier ways of solving this problem, but it is what it is.
The question goes as follows:
-I have a bash script that does some creates some files.
-I have to pass this first argument of this script ( which is a directory ) to another script trough a pipeline ( | ).
The problem comes in when I do try and pass this directory as an argument, in the other script, the argument I receive is null.
This is the whole code, in this exact order.
Nothing was left out.
The first script receives at least 11 argument:
if [[ $# -lt 11 ]]; then
echo "Not enough arguments." >&2;
exit 1;
fi;
The script will check if the first argument is an actual directory:
if [[ ! -d $1 ]]; then
echo "Not a valid first argument." >&2;
exit 1;
fi;
Here, I'll save my first directory here so that I can shift, and then I'm going to declare more stuff that i need
Directory=$1;
shift;
N=$#
name_array=("$#")
Pos=0;
Afterwards, the script will use all the other arguments to create .txt files with those names, and add a number of non-null lines in each .txt file ( So, argument2.txt has N-1 lines, argument3.txt has N-2 lines ... argumentN.txt has 1 line.) Afterwards, I have to change their permissions to 600.
while [[ $# -gt 0 ]]; do
if [[ -a "$Directory"/"$1".txt ]]; then
rm "$Directory"/"$1".txt;
fi
touch "$Directory"/"$1".txt
for((i=0;i<N;i++)); do
echo "$N" >> "$Directory"/"$1".txt;
done;
chmod u=rw "$Directory"/"$1".txt;
N=$(($N-1));
name_array["$Pos"]="$Directory"/"$1".txt;
Pos=$(($Pos + 1));
shift;
done;
Problem comes here, I'm trying to echo the first directory..
echo $Directory;
I have tried this as well, with the same results as the one above
echo "$(cd "$(dirname "$Directory")" && pwd -P)/$(basename "$Directory")";
.. like this so I can use in the Shell the pipeline commands.
The second script will receive the directory, enter it and search trough all the files that have been created just now.
In the Unix terminal, I use this:
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12 | ./SecondScript.bash
Thank you in advance!
Thanx for being honest, and of course we're not going to do your homework, but,...
Most of the code seems pretty good. There are some smaller issues, which you might easily solve with shellcheck (if it isn't installed, install it or use the web version), and I would not put ; at the end of each line.
So, instead of running the complete pipeline at once, break it down to find where your problem lies. If you run
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12
If the output is as expected (so, with the echo $Directory), and the files are created, then the first script is good. So, in this case, the directory 1 will contain the files 2 3 4 5 6 7 8 9 10 11 12, and the output will be 1.
Then, you will run the second script
./SecondScript.bash
and as input you will give:
1
^D
(that is control-d on Unix). And then the second script should do what you want (or not).
In this way you can debug the scripts separately.
(quick face-palm question: is 1 really the directory you want, or did you just forget the directory name as first argument?)
So far i am able to read in the files from a single folder on my Ubuntu using:
for i in /path/to/files/Folder1/*.pcd
do
if [ ! -z $last_i ]
then
./vapp $last_i $i
fi
last_i="$i"
done
this will read all files in Folder1. I Also have folder 2 and 3 (i.e. Folder2, Folder3). Inside each folder are several 100 files which are simply numbered such as 0000.pcd, 0001.pcd ... 0129.pcd... and so on.
I have tried to use
/path/to/files/Folder{1..3}/*.pcd
The problem is that it takes now all files from one folder and processes two files within, than goes through all files in this folder the same way before moving on to the next folder.
What i really want is to take from each of my three folders the ith filename e.g. 000i.pcd and pass it (including the path) to my application to do some calculations.
effectively I want to do this:
./vapp /Folder1/000i.pcd /Folder2/000i.pcd /Folder3/000i.pcd
Using native bash features alone, with its extended glob features. Run the script from /path/to/files/
#!/bin/bash
shopt -s globstar nullglob dotglob
i=0
end=129
while [ "$i" -le "$end" ]
do
# Generating the file-name to be generated, the numbers 0-129
# with 4 character padding, taken care by printf
file="$(printf "%04d.pcd" $i)"
# The ** pattern enabled by globstar matches 0 or more directories,
# allowing the pattern to match to an arbitrary depth in the current
# directory.
fileList=( **/"$file" )
# Remember to un-comment the below line and see if the required files
# are seen by doing
# printf "%s\n" "${fileList[#]}"
# and run the executable below
./vapp "$(printf "%s " "${fileList[#]}")"
done
The usage of extglob features are re-used from this wonderful answer.
I have solved my problem yesterday in a similar way to what Inian suggested. Except that my way is the manual way!
It works for me but I agree that Inian's way is much more flexible. Here is my solution anyway:
j=0
leadingZeros1="00000000"
leadingZeros2="0000000"
leadingZeros3="000000"
fol0="lidar03_00/"
fol1="lidar03_01/"
fol2="lidar03_02/"
ext=".pcd"
basepath="/my/path/"
for i in /my/path/lidar03_00/*.pcd
do
if [ ! -z $last_i ]
((j+=1))
then
if [ $j -le 9 ]
then
mypath1=$basepath$fol0$leadingZeros1$j$ext
mypath2=$basepath$fol1$leadingZeros1$j$ext
mypath3=$basepath$fol2$leadingZeros1$j$ext
fi
sleep .009
if [ $j -ge 10 ]
then
if [ $j -le 99 ]
then
mypath1=$basepath$fol0$leadingZeros2$j$ext
mypath2=$basepath$fol1$leadingZeros2$j$ext
mypath3=$basepath$fol2$leadingZeros2$j$ext
fi
if [ $j -ge 100 ]
then
if [ $j -le 999 ]; then
mypath1=$basepath$fol0$leadingZeros3$j$ext
mypath2=$basepath$fol1$leadingZeros3$j$ext
mypath3=$basepath$fol2$leadingZeros3$j$ext
fi;
fi
fi
#echo $mypath1
#echo $mypath2
#echo $mypath3
./vapp -i $mypath1 $mypath2 $mypath3 .txt"
fi
last_i="$i"
done
I admit, it is not the nicest solution but it fixes my problem for now. If i need to do this again I will probably do it Inian's way.
Thanks all for the help.
I have tried to avoid the nested ifs using logical AND (&&) but somehow it didn't work and I didn't wanted to spend more time on this. Thus the clumsy way...
I want to move some files in a directory, using their filename length as the criteria.
For example, I want to move any files longer that 10 characters.
I assumed I need an if loop in bash script, but I'm not sure how to proceed.
use this template
for f in *; do if [ ${#f} -gt 10 ]; then echo $f; fi; done
replace echo with your mv command.
note that directories will be in the list too.
so I am trying to make a bash script loop that takes a users file name they want and the number of files they want and creates empty files. I made the script but I keep getting the error "./dog.sh: line 6: 0]: No such file or directory". I'm new to bash script and don't know what I'm doing wrong. Any help would be awesome thanks!
#!/bin/bash
echo "what file name do you want?"; read filename
echo "how many files do you want"; read filenumber
x=$filenumber
if [$x < 0]
then
touch $fiename$filenumber
x=$((x--))
fi
for x in $(seq "$filenumber"); do touch "$filename$x"; done
seq $filenumber produces a list of numbers from 1 to $filenumber. The for loop assigns x to each of these numbers in turn. The touch command is run for each value of x.
Alternative
In bash, if we can type the correct file number into the command line, the same thing can be accomplished without a for loop:
$ touch myfile{1..7}
$ ls
myfile1 myfile2 myfile3 myfile4 myfile5 myfile6 myfile7
{1..7} is called "brace expansion". Bash will expand the expression myfile{1..7} to the list of seven files that we want.
Brace expansion does have a limitation. It does not support shell variables. So, touch myfile{1..$filenumber} would not work. We have to enter the correct number in the braces directly.
Maybe it's a typo: $fiename instead of $filename
also, you might want some kind of loop like so:
x=1
while [ $x -le $filenumber ]; do
touch $filename$x
let x=x+1
done
#!/bin/bash
echo "what file name do you want?"; read filename
echo "how many files do you want"; read filenumber
x=$filenumber
while [ $x -gt 0 ]; do
touch $filename$x
x=$(( $x - 1))
done
I'd like to change the file name suffix from files (using a bash script), but sometimes there are files with one period and some with two.
Now I use this:
new_file=`echo ${file} | sed 's/\(.*\.log.*\)'${suf}'/\1.'${num}'/'`
Where 'new_file' is the new file name, 'file' the original file name, '${suf}' the file's suffix and ${num} a new number.
So some.log must become some.log.1 and some.log.1 must become some.log.2. With my code some.log becomes some.log.1, but some.log.1 remains some.log.1.
I hope I'm clear enough. I appreciate any advice (even not using sed).
Update:
#paxdiablo. Something went wrong testing I think.
Now I use this piece of code as test;
#!/usr/bin/bash
shft() {
for suff in {6..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
clear
folder=~/logs/*.log
for i in {1..20}; do
echo ${i}> ~/logs/some.log
for fspec in ${folder} ; do
shft "${fspec}"
done
done
Every thing works fine now. Sorry for the confusion.
If you're looking to roll over log files, and depending on how complex you need to get, I've used the following segment before:
#!/usr/bin/bash
# rollover.sh
# Rolls over log files in the current directory.
# *.log.8 -> *.log.9
# *.log.7 -> *.log.8
# : : :
# *.log.1 -> *.log.2
# *.log -> *.log.1
shft() {
# Change this '8' to one less than your desired maximum rollover file.
# Must be in reverse order for renames to work (n..1, not 1..n).
for suff in {8..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
for fspec in *.log ; do
shft "${fspec}"
#date >"${fspec}" #DEBUG code
done
This will automatically roll over log files up to version 9 although you can just change the suff for loop to allow more.
With that DEBUG added so new files are created automatically for testing, the following transcript shows it in action:
pax> touch qq.log ; ./rollover.sh
Moving "qq.log" to "qq.log.1"
pax> touch "has spaces.log" ; ./rollover.sh
Moving "has spaces.log" to "has spaces.log.1"
Moving "qq.log.1" to "qq.log.2"
Moving "qq.log" to "qq.log.1"
pax> ll *log*
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 has spaces.log
-rw-r--r-- 1 pax None 0 2010-09-11 20:39 has spaces.log.1
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 qq.log
-rw-r--r-- 1 pax None 30 2010-09-11 20:38 qq.log.1
-rw-r--r-- 1 pax None 0 2010-09-11 20:38 qq.log.2
The good thing about this script is that it's easily configurable to handle a large amount of history (by changing the {8..1} bit), handles names with spaces, and handles gaps relatively robustly if log files go missing.
To rotate logs, you should really use logrotate.
If you can't rely on logrotate being available, here's a way do it inside the shell. To keep things simple, I'll assume that nothing else (including another instance of your script) will try to rename the log files while your script is running.
The easiest approach is to recursively rename log N+1 before actually renaming log N to N+1. The shell can perform all the necessary arithmetic, you don't need sed here. Note that while recursive functions are possible in a POSIX shell, there are no local variables other than the positional parameters (many shells offer local variables as an extension).
#!/bin/sh
## Move "$1.$2" to "$1.$(($2+1))", first rotating the target as well.
rotate () {
if [ -e "$1.$(($2+1))" ]; then rotate "$1" $(($2+1)); fi
mv -- "$1.$2" "$1.$(($2+1))"
}
for x; do
## Break each argument into FILE.NUMBER or just FILE.
suffix=${x##*.}
case $suffix in
*[!0-9]*)
if [ -e "$x.0" ]; then rotate "$x" 0; fi
mv -- "$x" "$x.0";;
*) rotate "${x%.*}" "$suffix";;
esac
done
Regarding what you've written, note that echo ${file} is bad for two reasons: most importantly, if ${file} contains any special characters such as white space, the shell will interpret them; also, with some shells, echo itself will interpret backslashes and possibly a leading -. So you should always write printf %s "$file" instead.