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.
Related
I have a hundreds of image files in a structure like this:
path/to/file/100/image1.jpg
path/to/file/9999/image765.jpg
path/to/file/333/picture2.jpg
I'd like to remove the 4th part of the path (100,9999,333, ...) so that I get this:
path/to/file/image1.jpg
path/to/file/image765.jpg
path/to/file/picture2.jpg
In this case the image file names have no duplicates and the the target directory could be named entirely different if this makes things easier (e.g. target could be "another/path/to/the/images/image1.jpg"
The solution might be some combination of find/cut/rename command.
How can I do this in bash?
Since you only have "hundreds" of files, it's quite possible that you don't need to do anything special, and can just write:
mv path/to/file/*/*.jpg path/to/file/
But depending on the number of files and lengths of their names, this may turn out to be more than the kernel will let you pass to a single command, in which case you may need to write a for-loop instead:
for file in path/to/file/*/*.jpg ; do
mv "$file" path/to/file/
done
(Of course, this assumes you have mv on your path. There's no Bash builtin for renaming a file, so any approach will depend on what else is available on your system. If you don't have mv, you'll need to adjust the above accordingly.)
I recommend using ruakh's solution if it will work, but if you need to explicitly test for those numeric directories, here's an alternative.
I'm just using echo to pipe the list of names in, and to show the mv at the end, but you could use find (example in a comment) and remove the echo on the mv to make it live.
IFS=/
echo "path/to/file/100/image1.jpg
path/to/file/9999/image765.jpg
path/to/file/333/picture2.jpg" |
# find path/to/file -name "*.jpg" |
while read -r orig
do this=""
read -a line <<< "$orig"
for sub in "${line[#]}"
do if [[ "$sub" =~ ^[0-9]+$ ]]
then continue
else this="$this$sub/"
fi
done
old="${line[*]}"
echo mv "$old" "${this%/}"
done
mv path/to/file/100/image1.jpg path/to/file/image1.jpg
mv path/to/file/9999/image765.jpg path/to/file/image765.jpg
mv path/to/file/333/picture2.jpg path/to/file/picture2.jpg
This question already has answers here:
Looping over pairs of values in bash [duplicate]
(6 answers)
Closed 6 years ago.
I have a bash script that looks through a directory and creates a .ppt from a .pdf, but i want to be able to check to see if there is a .pdf already for the .ppt because if there is I don't want to create one and if the .pdf is timestamped older then the .ppt I want to update it. I know for timestamp I can use (date -r bar +%s) but I cant seem how to figure out how to compare the files with the same name if they are in the same folder.
This is what I have:
#!/bin/env bash
#checks to see if argument is clean if so it deletes the .pdf and archive files
if [ "$1" = "clean" ]; then
rm -f *pdf
else
#reads the files that are PPT in the directory and copies them and changes the extension to .pdf
ls *.ppt|while read FILE
do
NEWFILE=$(echo $FILE|cut -d"." -f1)
echo $FILE": " $FILE " "$NEWFILE: " " $NEWFILE.pdf
cp $FILE $NEWFILE.pdf
done
fi
EDITS:
#!/bin/env bash
#checks to see if argument is clean if so it deletes the .pdf and archive files
if [ "$1" = "clean" ]; then
rm -f *pdf lectures.tar.gz
else
#reads the files that are in the directory and copies them and changes the extension to .pdf
for f in *.ppt
do
[ "$f" -nt "${f%ppt}pdf" ] &&
nf="${f%.*}"
echo $f": " $f " "$nf: " " $nf.pdf
cp $f $nf.pdf
done
To loop through all ppt files in the current directory and test to see if they are newer than the corresponding pdf and then do_something if they are:
for f in *.ppt
do
[ "$f" -nt "${f%ppt}pdf" ] && do_something
done
-nt is the bash test for one file being newer than another.
Notes:
Do not parse ls. The output from ls often contains a "displayable" form of the filename, not the actual filename.
The construct for f in *.ppt will work reliably all file names, even ones with tabs, or newlines in their names.
Avoid using all caps for shell variables. The system uses all caps for its variables and you do not want to accidentally overwrite one. Thus, use lower case or mixed case.
The shell has built-in capabilities for suffix removal. So, for example, newfile=$(echo $file |cut -d"." -f1) can be replaced with the much more efficient and more reliable form newfile="${file%%.*}". This is particularly important in the odd case that the file's name ends with a newline: command substitution removes all trailing newlines but the bash variable expansions don't.
Further, note that cut -d"." -f1 removes everything after the first period. If a file name has more than one period, this is likely not what you want. The form, ${file%.*}, with just one %, removes everything after the last period in the name. This is more likely what you want when you are trying to remove standard extensions like ppt.
Putting it all together
#!/bin/env bash
#checks to see if argument is clean if so it deletes the .pdf and archive files
if [ "$1" = "clean" ]; then
rm -f ./*pdf lectures.tar.gz
else
#reads the files that are in the directory and copies them and changes the extension to .pdf
for f in ./*.ppt
do
if [ "$f" -nt "${f%ppt}pdf" ]; then
nf="${f%.*}"
echo "$f: $f $nf: $nf.pdf"
cp "$f" "$nf.pdf"
fi
done
fi
Trying to create a while loop to remove 8 files from a specified test folder. I keep getting the error no such file or directory even though I am positive I am in the right folder because I can use the ls command to see the files... Anyways here is what I have
#!/bin/bash
var=(`ls ~/Random/Unit1/Test`)
x=${#var[#]}
i=0
while [ $i -lt $x ] ; do
rm $var # this line is incorrect and needs changing
((i++))
done
var is an array variable, right now you're accessing it as a scalar, which in bash returns the first value, so you remove the first file and then try to remove it again once for every file in the directory. If you want to remove every file you need to get the value of the array at every index, so in the loop you would get the nth value in the array, ie
rm ${var[$i]}
There are a couple of problems with your script, the first one is that you should cd to the folder from where you want to remove the files, you can use pushd and popd for it. Second, you should enclose the var variable with double quotes. Also, as stated in #redball's answer, you are accessing an array, you have to use array notation on it.
#!/bin/bash
DIRECTORY=~/Random/Unit1/Test
var=(`ls $DIRECTORY`)
x=${#var[#]}
i=0
# Saves current directory and change it to the one pointed by "$DIRECTORY"
push "$DIRECTORY"
while [ $i -lt $x ] ; do
rm "${var[$i]}"
((i++))
done
# Restores the previously saved directory
popd
I have a directory with files frame* (schema) and input_frame* (schema), where frame and input_frame are prefixes for two different types of files. If one takes just the characters after the prefixes and compares the two file lists, then the set of files frame* is always a subset of the set input_frame*.
I'd like to remove the files in input_frame* that don't have an equivalent member in frame*. Is there an easy way to do this in bash?
You can use:
for f in input_frame*; do [[ ! -f "${f#input_}" ]] && echo rm "$f"; done
Once you're satisfied with the output remove echo.
Something like this (test it first by using echo instead of rm):
for i in input_frame*;
if [ ! -e ${i/input_/} ]; then
rm $i
fi
done
Since frame is itself a suffix of input_frame, you can accomplish this with a simple use of the # parameter expansion operator.
for f in input_frame*; do
[[ -f "${f#input_}" ]] || rm "$f"
done
For example, if f is input_frame97, then ${f#input_} expands to frame97. Just check if the modified file name exists, and if not, remove the original.
I have loads of files in a folder. I want to do two things:
prefix them with xxx three digit serial numbers - ascending: 001 002 and so on
remove the prefix from their names, so 001a.xyz = a.xyz
I intend to do this using a simple bash script. What's the most elegant and simple to understand way to do this?
edit
the files are on a removable device, and I cannot seem to set chmod +X on the script on the device. So how do I run a script from my home directory which will change the files in another directory?
To add prefixes:
counter=1
for f in *; do
printf -v prefix_str '%03d' "$((counter++))"
mv "$f" "${prefix_str}$f"
done
To remove prefixes (caution -- this may overwrite if you have two files with the same suffix but different prefixes):
for f in [0-9][0-9][0-9]*; do
mv "$f" "${f:3}"
done
Use mv -n to avoid overwriting when two files have the same suffix.
This should work:
#!/bin/bash
count=1
for file in *; do
if [[ $file =~ [0-9][0-9][0-9].* ]]; then
sfile="${file:3}"
new=$(printf "%03d" ${count})
mv "$file" "${new}${sfile}"
((count++))
else
new=$(printf "%03d" ${count})
mv "$file" "${new}${file}"
((count++))
fi
done
What this script does is, checks for a given file in the current directory. If the file has a prefix already it will remove it and assign a new sequential prefix. If the file has no prefix it will add a sequential prefix to it.
The end result should be, all the files in your current directory (some with and some without prefixes) will have a new sequential prefixes.