Count filename characters, then copy those files to another directory - bash

I need to write a bash script that copies the files to dir2 that match the character count in their filename with a given int value given as an argument to the script. I've tried to do something but I cannot manage to get the files copied at all.
read number
list=`for file in *; do echo -n "$file" | wc -m; done`
for file in $list
do
if [ $file -eq $number ]
then
cp file dir2
fi
done

In your code, list is a list of filename lengths, not filenames. So $file is just a number. You also missed the leading $ on $file.
You don't need t use the wc program, you can get the length of a variable name using ${#name}. I think you need something like this:
while [[ $number != +([0-9]) ]]
do
read -p "Enter number: " number
done
for file in *
do
if (( ${#file} == $number ))
then
cp "$file" dir2
fi
done

Related

Compare the contents of all files inside a directory

I have a directory with multiple txt files, I need to compare the contents of each txt file and print the output as "Yay all files are same" else " Oops, File are not same"
cd tmp_dir
ls
abc.txt cde.txt fgh.txt ... xyz.txt
[ tmp_dir ]$ cat abc.txt
2022-08-01_20:14:36
[ tmp_dir ]$ cat def.txt
2022-08-01_07:40:29
[ tmp_dir ]$
How to loops through files and compare the contents
for file in tmp_dir/*; do
if [ -f "$file" ]; then
cmp -s -- files # need to compare all the files under directory
fi
done
Expected Output:
#If contents are same [Output should be in Green color]
Yay, all files are same
#If contents are not same [In red color]
Oops, Files are not the same
This can be done in a single pipeline by hashing all of the files and then counting how many unique hashes you get. If the answer is 1, all of the files are the same.
distinct_hashes="$(
find dir/ -type f -exec sha512sum {} + | # hash all files in `dir/`
awk '{print $1}' | # strip file names from output
sort -u | # remove duplicate hashes
wc -l # count distinct hashes
)"
case "$distinct_hashes" in
0) echo "no files";;
1) echo "all the same";;
*) echo "not all the same";;
esac
Alternatively, you could use cmp as you tried, and it would be more efficient. You'll just have to manually loop over all of the files. Note that you don't have to compare all pairs of files, which would be O(n2); you can keep it O(n) by comparing each file to one other.
first_file=
same=1
for file in dir/*; do
[[ -f "$file" ]] || continue
if [[ -z "$first_file" ]]; then
first_file="$file"
elif ! cmp -s "$file" "$first_file"; then
same=0
break
fi
done
if [[ -z "$first_file" ]]; then
echo "no files"
elif ((same)); then
echo "all the same"
else
echo "not all the same"
fi
Advanced shell scripters might point out the quotes in distinct_hashes="$(...)", case "$distinct_hashes", [[ -f "$file" ]], [[ -z "$first_file" ]], and first_file="$file" are unnecessary. I like to include optional quotes. Quoting variable expansions is a really important habit to develop and not everyone will know the intricacies of when they are and aren't required.

(unix) How to write script to count the number of files in a directory using loop

How can I create a bash script to count the number of files in a directory using a loop.
The script should take a target directory and output: Number of files in ::
#!/bin/bash
counter=0
if [ ! -d "$1" ]
then
printf "%s\n" " $1 is not a directory"
exit 0
fi
directory="$1"
number="${directory##*/}"
number=${#number}
if [ $number -gt 0 ]
then
directory="$directory/"
fi
for line in ${directory}*
do
if [ -d "$line" ]
then
continue
else
counter=$(( $counter + 1))
fi
done
printf "%s\n" "Number of files in $directory :: $counter"
I would use (GNU) find and wc:
find /path/to/dir -maxdepth 1 -type f -printf '.' | wc -c
The above find command prints a dot for every file in the directory and wc -c counts those dots. This would work well with any kind of special character (including whitespaces and newlines) in the filenames.
You don't really need a loop. The following will count the files in a directory:
files=($(ls $1))
echo ${#files[#]}

Pass script arguments as a pattern

I have a bash script that requires a glob expression as a parameter. However I am having trouble using inputs as globs i.e say my input is
Shell_script '*.c'
and my code is iterating through an array of files and filtering them through pattern matching. In this case files which do not have the .c extension. (In this example, the first input could be any pattern otherwise)
count=${#array[#]}
for (( q = 0; q < count; q++ ));
do
if [[ ${array[q]} == $1 ]]; then
:
else unset array[q]
fi
done
.....
Any ideas?
Matching array contents against a glob is entirely possible:
#!/bin/bash
# this array has noncontiguous indexes to demonstrate a potential bug in the original code
array=( [0]="hello.c" [3]="cruel.txt" [5]="world.c" )
glob=$1
for idx in "${!array[#]}"; do
val=${array[$idx]}
if [[ $val = $glob ]]; then
echo "File $val matches glob expression $glob" >&2
else
echo "File $val does not match glob expression $glob; removing" >&2
unset array[$idx]
fi
done
Similarly, you can expand a glob against filesystem contents, though you'll want to clear IFS first to avoid string-splitting:
# here, the expectation is that your script would be invoked as: ./yourscript '*.c'
IFS=
for f in $1; do
[[ -e $f || -L $f ]] || { echo "No file matching $f found" >&2; }
echo "Iterating over file $f"
done
That said, in general, this is extremely unidiomatic, as opposed to letting the calling shell expand the glob before your script is started, and reading the list of matched files off your argument vector. Thus:
# written this way, your script can just be called ./yourscript *.c
for f; do
[[ -e $f || -L $f ]] || { echo "No file matching $f found" >&2; }
echo "Iterating over file $f"
done
You can loop over your list of files like this. If you run your script as
./test.sh "*.c". Then inside your script you can do:
for file in $1
do
#use your file
done

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

How do I manipulate filenames in 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

Resources