I have several .jpg images in a folder that have names like:
20140331_134927.jpg
20140331_124933.jpg
20140331_124933.jpg
etc..
I want to rename them to something like:
Agra-1.jpg
Agra-2.jpg
Agra-3.jpg
etc..
I tried running the following script (stored as my.sh):
for files in *.jpg; do
i=1
echo mv "$files" "Agra-$i.jpg"
i=$((i+1))
done
However, if I were to run that without the echo, all files would be renamed to "Agra-1.jpg"
Why does this not work as I expect and how should this be written?
Put the assignment out of the loop:
i=1 # only once
for files in *.jpg; do
mv "$files" "Agra-$i.jpg"
let i++
done
Here is an example - you should declare the counter variable outside of the loop otherwise it will be reset to its initial value on each iteration:
Inside loop:
$ for file in *; do i=1; echo $i; (( i++ )); done
1
1
1
Outside loop:
$ i=1
$ for file in *; do echo $i; (( i++ )); done
1
2
3
Related
I have N files in my directory, I want to be able to loop through them with batches, for example for 2/3/4 files I want to execute one command. I saw something like this, but can't find it, unfortunately. I will try to explain what I want using pseudo code, for example:
i=0
while i<N:
a=getFile(i)
b=getFile(i+1)
dosomething a
dosomething b
i+=2
Is there any way to do it in Bash? Right now I'm using regexp, but it gets one file at a time (I'm using *.ext because all files have one extension, so you can just loop through all files in the directory in your answer, if it's easier):
for j in *.ext; do
...
done
This would be the same with most programming languages: loop and store items somewhere until you have enough of them:
declare -i count=0
declare -a files=()
for f in *.ext; do
files[count]="$f"
(( count += 1 ))
if (( count == 2 )); then
dosomething "${files[0]}"
dosomething "${files[1]}"
count=0
fi
done
If you want to process your files in batches of more than 2 at a time we can also design something a bit more generic (adapt the value of batch):
declare -i batch=3
declare -i count=0
declare -a files=()
for f in *.ext; do
files[count]="$f"
(( count += 1 ))
if (( count == batch )); then
for (( i=0; i<batch; i++ )); do
dosomething "${files[i]}"
done
count=0
fi
done
You can collect your arguments into an array, and then slice that any way you like.
array=( *.ext )
for ((i=0; i<=${#array[#]}; i+=2)); do
dosomething "${array[i]}" "${array[i+1]}"
done
I am trying to allocate a bunch of temp files in a loop and export them from within the loop. I then want to loop thru again and echo the values.
for (( i=1; i<=5; i++ ))
do
dd if=/dev/zero of=/tmp/mh1_$i.out bs=1024 count=1024 status=none
declare TEMP_FILE_${i}="/tmp/mh1_${i}.out"
export "TEMP_FILE_${i}"
done
If I do a echo $TEMP_FILE_1 it correctly prints /tmp/mh1_1.out
But when I try this in a loop it prints 1, 2, 3, 4 and 5.
for (( i=1; i<=5; i++ ))
do
echo $TEMP_FILE_${i} --> This prints the i value instead of /tmp/mh1_x.out
done
How do I escape the index $i in the echo above to see the real file name ?
I suggest using the mktemp utility with arrays:
# create tmp files
tmp_files=()
for ((i=0;i<5;++i)); do
tmp_files+=("$(mktemp --tmpdir mh1_XXXXXX.out)") || exit 1
done
# process all tmp files
for file in "${tmp_files[#]}"; do
echo "$file"
# do something with the file ...
done
# list all tmp files
printf '%s\n' "${tmp_files[#]}"
# list 2nd tmp file
echo "${tmp_files[1]}"
In order to make your code work, you need to use variable indirection and change your second for loop to:
for ((i=1;i<=5;++i)); do
var=temp_file_$i
echo "${!var}"
done
Don't use uppercase variables as they could clash with environmental or internal shell variables. Also, note that export is needed only when you want to pass the variables to child processes in the environment.
Why not use bash arrays?
export temp_file
loop
temp_file[$i]="/tmp/mh1_${i}.out"
...
endloop
Then loop over the array
I have 100 subdirectories and I wanted to loop through the first ten of them in a bash for loop such like that:
for d in ./output/*[0..9];
do
echo $d
done
But the output seems not what I expected:
./output/405050
./output/405140
./output/405309
./output/405310
./output/405319
./output/500550
./output/500589
./output/500610
Why only 8 were printed and my question is how to select a fix number elements from this type of for loop.
*[0..9] loops over ones that end in a 0, 9, or .. If you had written *{0..9} that would loop over ones ending in a digit 0 through 9--closer, but still not right.
Try this loop, which reads the first 10 directory names in a loop. It's kinda obtuse. The primary idea is using while read ... < <(cmd) to read a command's output one line at a time. IFS= and -r are pedantic bits to handle directory names with whitespace and backslashes correctly.
while IFS= read -r dir; do
echo "$dir"
done < <(ls output/*/ | head -10)
Or use this more straightforward version with a counter:
i=0
for dir in output/*/; do
echo "$dir"
((++i < 10)) || break
done
Or this one storing the directories in an array:
dirs=(output/*/)
for dir in "${dirs[#]::10}"; do
echo "$dir"
done
You can make a counter:
#!/bin/bash
i=0;
for d in ./output/*/;
do
echo $d
echo ""
if [[ i == 10 ]]; then
break
fi
i+=1
done
With this you asure to get 10 folders.
I very important to do the last backslash to match only directories.
I am trying to move some files down a directory and rename them
the current file struct looks something like the following:
photos ->
{
1 -> Auction_Images -> <several image files>
item_image-1-1.jpg
item_image-2-1.jpg
...
2 -> Auction_Images -> <several image files>
item_image-3-1.jpg
...
3 -> Auction_Images -> <several image files>
my_script.sh
}
I want to be able to go into each numbered directory, check how many files/folders exist, if there is only one meaning the Auction_Images dir like folder 3 then I want to go into that Auction_Images dir and moved the photos out one directory and rename them. So far I have the following:
x=1
while [ $x -le 3 ]
do
cd $PWD/$x
echo Changed to dir: $PWD
count=ls | wc -l
echo $count
if [[ "$count" -eq 1 ]]
then
echo $PWD has 1 file/folder
fi
echo --------------------------------------------------
cd ..
x=$(( $x + 1 ))
done
the output I am getting is:
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/1
0
--------------------------------------------------
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/2
0
--------------------------------------------------
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/3
0
The expected output would be:
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/1
2
--------------------------------------------------
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/2
1
--------------------------------------------------
Changed to dir: /Users/jarvis/Desktop/imports/company/photos/3
0
The current problem I am having is that the count doesn't seem to be captured correctly.
If I understood the question correctly, you could use something like this:
#!/usr/bin/env bash
shopt -s nullglob extglob
for dir in +([0-9])/; do
files=("$dir/"*)
count=${#files[#]}
echo "$PWD/$dir"
echo "$count"
if (( count == 1 )); then
#do something
echo "Only one file/folder"
mv -- "$files" /some/path/newname
fi
echo "=============================="
done
shopt -s nullglob = ensures you will not execute the loop if there are no directories
shopt -s extglob = enables extended globbing
for dir in +([0-9])/ = loops only over directories consisted of one or more numbers
files=("$dir/"*) = stores all files in currently searched directory into an array
count=${#files[#]} = counts the number of elements in the array -- number of files
mv -- "$files" /some/path/newname = moves the first and only element in the files array into the new location (note that $files is a shorter way of ${files[0]})
If you don't like the for loop I used and you insist on using this:
while [ $x -le 3 ]; do
x=$(( $x + 1 ))
done
you might consider using the C-style for loop:
for (( x=1; x<=3; x++ )); do
#do something
done
or brace expansion:
for x in {1..3}; do
#do something
done
That way you don't have to increment $x yourself. On top of that:
Quote expansions = echo "$PWD"
count=ls | wc -l = use command substitution instead: count=$(ls | wc -l)
Why you shouldn't parse the output of ls
I've found most of the questions of this kind where the change in name has been same for the entire set of files in that directory.
But i'm here presented with a situation to give a different name to every file in that directory or just add a different prefix.
For Example, I have about 200 files in a directory, all of them with numbers in their filename. what i want to do is add a prefix of 1 to 200 for every file. Like 1_xxxxxxxx.png,2_xxxxxxxx.png...........200_xxxxxxxx.png
I'm trying this, but it doesnt increment my $i everytime, rather it gives a prefix of 1_ to every file.
echo "renaming files"
i=1 #initializing
j=ls -1 | wc -l #Count number of files in that dir
while [ "$i" -lt "$j" ] #looping
do
for FILE in * ; do NEWFILE=`echo $i_$FILE`; #swapping the file with variable $i
mv $FILE $NEWFILE #doing the actual rename
i=`expr $i+1` #increment $i
done
Thanks for any suggestion/help.
To increment with expr, you definitely need spaces( expr $i + 1 ), but you would probably be better off just doing:
echo "renaming files"
i=1
for FILE in * ; do
mv $FILE $((i++))_$FILE
done
i=1
for f in *; do
mv -- "$f" "${i}_$f"
i=$(($i + 1))
done