I am trying to adapt this script to work with a directory of .pdf files. What is different with my file structure is the use of leading 0's. My files are all 3 digits --- such as
001.pdf
002.pdf
...
045.pdf
046.pdf
...
124.pdf
125.pdf
Is it possible to make this work?
#!/bin/sh
start_number=1
current_number=0
errfound=0
errfiles=""
for file in $(ls); do
current_number="${start_number}"
file_error=0
while read line; do
if [ ! "${line}" = "${current_number}" ]; then
echo "Missing number: ${current_number}"
file_error=1
fi
done < $file
if [ "${file_error}" -ne 0 ]; then
errfiles="${errfiles}${file} "
fi
done
if [ ! -z "${errfiles}" ]; then
echo "The following files are missing numbers:"
echo "${errfiles}"
fi
exit 0
If you want to read the filenames from a file and report any missing names in that list:
awk '{ name = sprintf("%03d.pdf", ++n) }
{ while ($0 != name) {
printf("Missing file %s\n", name)
name = sprintf("%03d.pdf", ++n)
} }' <file
Given the file
001.pdf
002.pdf
005.pdf
007.pdf
008.pdf
This would generate
Missing file 003.pdf
Missing file 004.pdf
Missing file 006.pdf
Assuming you want to find missing files in a sequence with bash, where each filename should match the pattern <3 zero-filled digits>.pdf and where the digits should be between 001 and 125:
for name in {001..125}.pdf
if [ ! -f "$name" ]; then
printf 'file "%s" is missing\n' "$name" >&2
fi
done
or with /bin/sh:
i=1
while [ "$i" -le 125 ]; do
name=$( printf '%03d.pdf' "$i" )
if [ ! -f "$name" ]; then
printf 'file "%s" is missing\n' "$name" >&2
fi
i=$(( i + 1 ))
done
This iterates with i taking the values from 1 to 125. The expected filename is created using printf and the format specification %03d.pdf. The %03d is a placeholder for a value that will be converted to "a zero-filled 3-digit decimal integer" ($i will be used for this value).
If the expected filename does not exist as a regular file, a message is printed to standard error.
Using ksh93:
for name in {1..125%03d}.pdf
if [ ! -f "$name" ]; then
printf 'file "%s" is missing\n' "$name" >&2
fi
done
Related
I have a folder with a bunch of JPEGs and I want to check if their filenames meet my filename criteria: (1) no spaces, (2) no underscores, (3) no consecutive capital letters, (4) and each filename should end in "-original.jpg". I output any bad filenames, and then prompt the user to proceed or not. My script below works great for conditions (1), (2), and (4), but I want to add condition (3) as another elseif.
i=0 # Initialize issue counter
# Detect and indicate any filename issues
for file in *.jpg; do
if [[ $file = *" "* ]] | [[ $file = *"_"* ]]
then
echo "Filename issue: " $file
i=$((i+1))
elif [[ $file != *"-original.jpg" ]]
then
echo "Filename issue: " $file
i=$((i+1))
fi
done
# If condition satisfied, provide indication of no filename issues
if [[ $i == 0 ]]
then
echo "No filename issues"
fi
echo
# Prompt user if they want to continue with image processing
read -p "Proceed with image processing? (enter 'y' or 'n') " yn
case $yn in
[Yy]* )
echo
echo "PROCEED"
echo
break;;
[Nn]* ) echo; exit;;
* ) echo "Please answer yes or no. ";;
esac
I was able to cobble this together for condition (3) and it detects consecutive capitals, but I can't figure out how to make it output the larger string ($name) it's parsing and/or pass a variable back to the larger shell script. What's the best way to do this?
echo "$name" |
awk '{
for (i=2; i<=NF; i++) {
if ($i ~ /[A-Z]/ && $(i+1) ~ /[A-Z]/) {
echo "capitalization problem"
}
echo "no capitalization problem"
}
}'
Use the regexp pattern matching provided by bash using the =~ inside [[ ]]
if [[ $name =~ [[:upper:]]{2} ]]; then
printf 'Capitalize problem!\n'
else
prinf 'No capitalize problem.\n'
fi
As mentioned by #shawn, updated to [[:upper:]]{2}
You can use a simple grep pipeline to check for conestive uppercase characters, something like
pax:/> if echo aBbd | grep '[A-Z][A-Z]' >/dev/null ; then echo bad ; fi
pax:/> if echo aBBd | grep '[A-Z][A-Z]' >/dev/null ; then echo bad ; fi
bad
Just replace the fixed string being echoed with your actual file name.
You can use the *[[:upper:]][[:upper:]]* glob:
$ cd "$(mktemp --directory)"
$ touch {a,A}{b,B}{c,C}.jpg
$ ls
abc.jpg abC.jpg aBc.jpg aBC.jpg Abc.jpg AbC.jpg ABc.jpg ABC.jpg
$ ls *[[:upper:]][[:upper:]]*
aBC.jpg ABc.jpg ABC.jpg
This will be much faster than looping through the entire list of files and checking the pattern of them individually.
You can combine some of those tests together:
for file in *.jpg; do
if [[ $file =~ [[:space:]_]|[[:upper:]]{2} ]] || [[ $file != *-original.jpg ]]
then
echo "Filename issue: $file"
i=$((i+1))
fi
done
First use a regular expression to look for space, underscore, or two consecutive upper case letters, and then a wildcard pattern see if the file doesn't end in -original.jpg. If either test succeeds, it's an invalid filename. If both fail, it's good.
To simply and efficiently test for (1) no spaces, (2) no underscores, (3) no consecutive capital letters, (4) and each filename should end in "-original.jpg" using any POSIX awk in any shell:
awk '
BEGIN {
if ( ARGV[1] == "*.jpg" ) {
exit
}
for (i=1; i<ARGC; i++) {
fname = ARGV[i]
if ( (fname ~ /[[:space:]_]|[[:upper:]]{2}/) || (fname !~ /-original\.jpg$/) ) {
gotBad = 1
print "Filename issue:", fname | "cat>&2"
}
}
}
END {
if ( ! gotBad ) {
print "No filename issues" | "cat>&2"
}
exit gotBad
}
' *.jpg
I use in a function a variable that was defined in another function, from the same script, but I receive an error.
testare()
{
find "$Home" -name "$({!i})"
rez=$?
test -d "$rez"
t1=$?
test -f "$rez"
t2=$?
if [ "$t1" -eq 0 ]
then
return 0
elif [ "$t2" -eq 0 ]
then
return 1
else
return 2
fi
}
menu ()
{
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]
then
echo "Error!"
return
fi
for (( i=2; i <= "$#"; ++i ))
do
testare "$({!i})"
rez=$?
if [ "$rez" -eq 0 ]
then
echo "Director with the same name exists!"
fi
if [ "$rez" -eq 1 ]
then
echo "File with the same name already exists!"
fi
if [ "$rez" -eq 2 ]
then
touch "$({!i})"
fi
done
}
menu $#
What my code should do: I call my script with maximum 9 parameters, the first one indicates the location where i must create files with the names of the other parameters. First i have to check if those names arent already present on my disc. The usage of FOR is mandatory.
The error shows up on the **** line, because of the i variable. I think " i " isnt available at that moment. How could I make this work? :(
I tried also with writing in another file the function and source it on menu, same result..
You can eliminate testare and simply perform the script's function in one routine as follows:
#!/bin/bash
menu() {
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]; then
echo "Error!"
exit 1
fi
for (( i = 2; i <= "$#"; i++ )); do
if [ -d "$1/${!i}" ]; then
printf "Directory '%s' already exists! \n" "${!i}"
elif [ -f "$1/${!i}" ]; then
printf "File '%s' already exists! \n" "${!i}"
else
touch "$1/${!i}"
fi
done
exit 0
}
menu "$#"
But if you want to use the two routines as they are, then you can modify your script as follows:
testare() {
test -d "$1/$2"
t1="$?"
test -f "$1/$2"
t2="$?"
if [ "$t1" -eq 0 ]; then
return 0
elif [ "$t2" -eq 0 ]; then
return 1
else
return 2
fi
}
menu() {
if [ "$#" -lt 2 ] || [ "$#" -gt 9 ]; then
echo "Error!"
exit 1
fi
for (( i = 2; i <= "$#"; i++ )); do
testare "$1" "${!i}"
rez="$?"
if [ "$rez" -eq 0 ]; then
printf "Directory '%s' already exists! \n" "${!i}"
elif [ "$rez" -eq 1 ]; then
printf "File '%s' already exists! \n" "${!i}"
else
touch "$1/${!i}"
fi
done
exit 0
}
menu "$#"
Remember: when you are passing any variable as an argument, that parameter to the routine is accessed by $i where i is replaced by any number >=0 referring to the position of the argument from left to right.
For example, in your script, you had $({!i}) within testare, but the variable i is only defined in the menu routine, hence using that variable in testare results in errors. In order access the arguments passed to testare, you should either directly access them, ie. $1, $2 etc. or you should define a variable (in a loop, for example) and access them using that variable as ${!j} for some variable j.
Edit- explanation for first comment's questions:
Consider, for example, that you had an empty folder named dir in your current working directory. Now you want to create files one, two and three in the dir folder. Hence, you pass it to your script as:
$ ./script dir one two three
Thus, "$1"=dir, "$2"=one etc. The line test -d "$1/$2" tests whether $2 is a directory and exists within the $1 folder, ie. whether or not dir/one exists and is a directory. This is necessary because all files need to be tested and created within the specified directory, which always comes as the first argument to the script (as you stated).
In your script, since testare is doing the testing for existence of named file/directory, testare will need access to the dir directory, hence the reason for 2 arguments being passed to testare in the line testare "$1" "${!i}", whereby the first argument is the dir directory, and the second argument is the file to be tested.
As for your question on how many arguments a method should be called with, you should pass on as many arguments as needed to make the routine do what it is supposed to. For example, the routine testare needed to have the dir directory and some specified file, so that it can check whether that file exists within dir. Hence calling testare dir somefile by using testare "$1" "${!i}".
On the other hand, the %s in printf is a placeholder for "string", whose value is provided at the end. For example,
$ printf "This is not a placeholder for %s \n" "numbers"
This is not a placeholder for numbers
$ printf "The placeholder for numbers is %s \n" "%d"
The placeholder for numbers is %d
$ printf "pi as a whole number equals %d\n" 3
pi as a whole number equals 3
Edit 2: If you want to search the /home directory recursively to check whether somefile exists, you can do the following:
#!/bin/bash
file=$(find /home -name "somefile")
if [[ "$file" != "" ]]; then
echo "file exists"
else
echo "file does not exist"
fi
You can try this (comments and suggestions in the script) :
# Explicit function name
found() {
if [[ -f "$1" ]];then
foundtype="file"
# found() success, return 0
return 0
elif [[ -d "$1" ]]; then
foundtype="directory"
return 0
else
# found() failed, return 1
return 1
fi
}
menu () {
if [ $# -lt 2 ] || [ $# -gt 9 ]
then
# Explicit message sent to stderr
echo "Error : you must provide from 2 to 9 parameters" >&2
# exit script with 1 status code (failed)
exit 1
fi
destdir="$1"
shift
# loop over params
for file in "$#"
do
if found "$destdir/$file"; then # found value = found() return code
echo "$destdir/$file" "is an existing" "$foundtype";
continue; # next iteration
else
# create destination dir if doesn't exist (as dir or as file)
[ ! -d "$destdir" ] && [ ! -f "$destdir" ] && mkdir "$destdir"
echo "Creating $destdir/$file" && touch "$destdir/$file"
fi
done
}
menu "$#"
I made this code
if [ $# -ne 2 ]; then
echo "use $0 dir1 dir2"
exit 1
fi
if [ ! -d $1 ]; then
echo "$1 nu este un director"
exit 1
fi
if [ ! -d $2 ]; then
echo "$2 nu este un director "
exit 1
fi
a=0
k=1
for $1 in `ls`
do
if [ -f $1 ]; then
a=`exp $a + 1`
fi
done
echo "Ther are $a file "
I want to compare two folders and the folder are arguments to the command line.. it should be something like this : ./script.sh dir1 dir2
But i have this eror :
**./director.sh: line 29: `$1': not a valid identifier
**
I want to count the file from dir1 who is argument to the command line.
Can someone help me please ?
This is the main error:
for $1 in `ls`
$1 is not a valid variable name
don't parse ls
Do this instead
for file in *
Also, quote your variables: you want to protect your script from any filenames containing whitespace.
if [ ! -d "$1" ]
if [ -f "$file" ]
Instead of this part:
a=0
k=1
for $1 in `ls`
do
if [ -f $1 ]; then
a=`exp $a + 1`
fi
done
do this:
a=$(ls "$1" | wc -l)
If you absolutely have to use your looping, change it like this:
a=0
for i in ${1}/*
do
if [ -f "$i" ]; then
let a=a+1
fi
done
echo "There are $a files"
I need to create one file list for below files:
APPLE_001
APPLE_002
BBB_004
APPLE_003
I need to create file_list only for
APPLE_001
APPLE_002
APPLE_003
Thanks,
Ipsita
Your specifications are not that narrow, but here a bash script that match your request :
#!/bin/bash
if [[ $# < 2 ]]
then
echo "[ERROR] this script expects two arguments, input file and output file" >&2
exit 1
fi
input_file=$1
output_file=$2
if [[ ! -f $input_file ]]
then
echo "[ERROR] your input file '$input_file' is missing" >&2
exit 1
fi
if [[ -f $output_file ]]
then
echo "[ERROR] your output file '$ouput_file' already exists please move it away" >&2
exit 1
fi
while read LINE
do
if [[ $LINE =~ APPLE_[0-9]+ ]]
then
echo $LINE >> $output_file
else
echo "'$LINE' does not match expected pattern, skip it"
fi
done < $input_file
if [[ -f $ouput_file ]]
then
echo "'$output_file' generated."
else
echo "[WARNING] no pattern found in '$input_file' no file generated"
fi
make it executable ( chmod +x ./list_starting_with.sh )
run it with ./list_starting_with.sh file_in.txt file_out.txt
I have created a script which creates two files and reverses their contents if both the files are different. I cant understand how to resolve this error :
[: my-filename: unexpected error in shell programming
Code:
echo "Enter first filename :"
read file1
echo "Enter second filename:"
read file2
echo "Enter content of file 1 : "
gedit $file1
echo "Enter content of file 2 : "
gedit $file2
check=" "
x=` cmp $file1 $file2 `
if [ $x -eq $check ]
then
echo "Both files are same"
rm $file2
echo "Redundant file removed!!!"
else
echo "They are different"
fi
tac $file1 | cat > temp1
rev temp1 | cat > temp11
tac $file2 | cat > temp2
rev temp2 | cat > temp22
mv temp11 $file1
mv temp22 $file2
echo "Content of both the files reversed"
You can use cmp directly from the if statement
if cmp -s $file1 $file2
then
echo "Both files are same"
rm $file2
echo "Redundant file removed!!!"
else
echo "They are different"
fi
You have to give which shell you are using.
But I think if [ $x -eq $check ] cause the problem. Your shell interpret the file name which include the '-' as a switch. you can either rename my-filename to myfilename or double quote the $x:
if [ "$x" -eq "$check" ]
In bash, the logical operators -eq -ne -gt -lt -ge -le are specific for numeric arguments. For comparing string you can simply use
if [ $x = $check ]; then
# do whatever
fi