What is the error in this shell script - linux

I never used shell script, but now I have to , here is what I'm trying to do :
#!/bin/bash
echo running the program
./first
var = ($(ls FODLDER |wc -l)) #check how many files the folder contains
echo $var
if( ["$var" -gt "2"] #check if there are more the 2file
then ./second
fi
the scriopt crashes at the if statement. how may I solve this

Many:
var = ($(ls FODLDER |wc -l))
This is wrong, you cannot have those spaces around =.
if( ["$var" -gt "2"]
Your ( is not doing anything there, so it has to be deleted. Also, you need spaces around [ and ].
All together, this would make more sense:
#!/bin/bash
echo "running the program"
./first
var=$(find FOLDER -maxdepth 1 -type f|wc -l) # better find than ls
echo "$var"
if [ "$var" -gt "2" ]; then
./second
fi
Note:
quote whenever you echo, specially when handling variables.
see another way to look for files in a given path. Parsing ls is kind of evil.
indent your code for better readibility.

Edit your script.bash file as follow:
#!/bin/env bash
dir="$1"
echo "running the program"
./first
dir_list=( $dir/* ) # list files in directory
echo ${#dir_list[#]} # count files in array
if (( ${#dir_list[#]} > 2 )); then # test how many files
./second
fi
Usage
script.bash /tmp/
Explaination
You need to learn bash to avoid dangerous actions!
pass the directory to work with as first argument in the command line (/tmp/ → `$1)
use glob to create an array (dir_list) containing all file in given directory
count items in array (${#dir_list[#]})
test the number of item using arithmetic context.

Related

How can I remove the extension of specific files in a directory?

I want to remove the extension of specific files with a given extension.
So for instance, in a directory foobar, we have foo.txt, bar.txt foobar.jpg.
Additionally, the extension that I've put in to be removed is txt
After calling the program, my output should be foo bar foobar.jpg
Here is my code so far:
#!/bin/bash
echo "Enter an extension"
read extension
echo "Enter a directory"
read directory
for file in "$directory"/*; do //
if [[ $file == *.txt ]]
then
echo "${file%.*}"
else
echo "$file"
fi
done
However when I run this on a given directory, nothing shows up.
I'm assuming that there is a problem with how I referred to the directory ( in the line where I placed a //) and I've tried to research on how to solve it but to no avail.
What am I doing wrong?
If files do exist in a valid directory you've entered then they should show up — with one exception. If you are using ~/ (shorthand home directory) then it will be treated as plain text in your for loop. The read variable should be substituted into another variable so the for loop can treat it as a directory (absolute paths should work normally as well).
#!/bin/bash
echo "Enter an extension"
read -r extension
echo "Enter a directory"
read -r directory
dir="${directory/#\~/$HOME}"
for file in "$dir"/*; do
if [[ $file == *."$extension" ]]
then
echo "${file%.*}"
else
echo "$file"
fi
done
You can simplify your for-loop:
for file in "$directory"/*; do
echo "${f%.$extension}";
done
The % instructions removes only matching characters. If nothing matches, the original string (here f) is returned.
When you write bash scripts it's more common to pass arguments to your script via command line arguments rather than by reading it from standard input via read program.
Passing arguments via command line:
#!/bin/bash
# $# - a bash variable which holds a number of arguments passed
# to script via command line arguments
# $0 holds the name of the script
if [[ $# -ne 2 ]]; then # checks if exactly 2 arguments were passed to script
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
echo $1; # first argument passed to script
echo $2; # second arugment passed to script
This approach is more efficient because a subprocess is spawn for read command to run and there is no subprocess spawn for reading command line arguments.
There is no need to manually loop through directory, you can use find command to find all files with given extension within given directory.
find /path/to/my/dir -name '*.txt'
find $DIRECTORY -name "*.$EXTENSION"
# note that single quotes in this context would prevent $EXTENSION
# variable to be resolved, so double quotes are used " "
# find searches for files inside $DIRECTORY and searches for files
# matching pattern '*.$EXTENSION'
Note that to avoid bash filename expansion sometimes it is required to wrap actual pattern in single quotes ' ' or double quotes " ". See Bash Filename Expansion
So now your script can look like this:
#!/bin/bash
if [[ $# -ne 2 ]]; then
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
$EXTENSION = $1 # for better readability
$DIRECTORY = $2
for file in `find $DIRECTORY -name "*.$EXTENSION"`; do
mv $file ${file%.$EXTENSION}
done
Construct ${file%.$EXTENSION} is called Shell Parameter Expansion it searches for occurrence of .$EXTENSION inside file variable and deletes it.
Notice that in the script it is easy to pass extension as directory and vice versa.
We can check if second argument is in fact directory, we can use following construction:
if ! [[ -d $DIRECTORY ]]; then
echo $DIRECTORY is not a dir
exit -1
fi
This way we can exit from the script earlier with more readable error.
To sum up entire script could look like this:
#!/bin/bash
if [[ $# -ne 2 ]]; then
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
EXTENSION=$1 # for better readability
DIRECTORY=$2
if ! [[ -d $DIRECTORY ]]; then
echo $DIRECTORY is not a directory.
exit -1
fi
for file in `find $DIRECTORY -name "*.$EXTENSION"`; do
mv $file ${file%.$EXTENSION}
done
Example usage:
$ ./my-script.sh txt /path/to/directory/with/files

Copy multiple files with bash script from command line arguments?

I want to create a script that allows me to enter multiple filenames from the command line, and have the script copy those files to another directory. This is what I am trying but I keep getting an error of
line 10: binary operator expected
#!/bin/bash
DIRECTORY=/.test_files
FILE=$*
if [ -e $DIRECTORY/$FILE ]; then
echo "File already exists"
else
cp $FILE $DIRECTORY
fi
So if the script was named copfiles.sh, I am writing...
./copyfiles.sh doc1.txt doc2.txt
It will move the files, but if they already exist, it won't read the error message.
Also I get the "line 10: binary operator expected" error regardless of it the files are there or not. Can anyone tell me what I am doing wrong?
As a possible problem, if you had a filename with a space or had multiple arguments $* would have spaces in it so [ -e $DIR/$FILE ] will expand to have lots of words, like [ -e /.test_files/First word and more ] and -e expects just 1 word after it. Try putting it in quotes like
if [ -e "$DIRECTORY/$FILE" ]
Of course, you may only want to store $1 in $FILE to get just the first argument.
To test all the arguments you want to loop over the arguments and test each with something like
for FILE in "$#"; do
if [ -e "$DIRECTORY/$FILE" ]; then
echo "$FILE already exists"
else
cp "$FILE" $DIRECTORY
fi
done
Using quotes around $# to preserve spaces in the original arguments as well

creating Unix script to check for directories and subdirectories in

I'm completing the following for one of my assignments using Korn shell.
For each argument in the argument list (which becomes the current pathname):
Check whether the current pathname is a directory, and if so:
Initialize a variable maxsubdir with the null (empty) string, and
a maxentries variable to 0;
For each entry in the directory check if that entry represents a
directory and if so, find the numbers of entries in that
subdirectory with a pipe consisting of ls -l and wc, and save the
result in a variable named curentries.
Compare curentries with maxentries, and if curentries is greater,
update maxsubdir and maxentries. (--10 points)
When the for cycle for a directory is completed, display (with
echo) the directory name, maxsubdir and maxentries (with appropriate
explanatory text.)
If the pathname in a) is not a directory, display the pathname
and an explanatory text saying that the pathname does not represent
a directory.
Go to the next command line argument (pathname) and repeat 1-7
The execution of the script ends when all pathnames are processed (the while is completed )
This is the code I have for it so far (EDITED):
#!/bin/ksh
directoy=$1
while [ $# -ne 0 ]; do
if [ -d $1 ]; then
maxsubdir=
maxentries=0
for x in $1; do
echo "Checking if $1 represents a directory..\n"
curentries="ls -l | wc"
if [ $curentries > $maxentries ]; then
maxentries=$curentries
maxsubdir=$curentries
fi;
done
echo "The directory structure of $1 is … \n"
echo "Maximum sub directories: \n"
echo "$maxsubdir\n"
echo "Maximum directory entries: \n"
echo "$maxentries"
fi
done
Where do I need to insert the "shift" command since I Unix can only handle a limited number of arguments?
Is my syntax appropriate? Or do I have syntax errors on sort lines?
Script seems to run but does not produce output to screen? Perhaps it's endless?
Have a look here and see if this helps out. Explanations are in the code.
#!/bin/ksh
directory=$1
# check whether the entered path is a directory
if [ -d $1 ];then # yes, it's a directory
maxsubdir=null
maxentries=0
echo "$1 is a directory"
# you are only counting lines, add -l to wc
# also you have to not count the first line. it's returns the size
curentries=`ls -l $1 | wc -l`
echo ${curentries}
fi
You don't.
You do have some errors.
Or perhaps, it never reaches that code?
Your assignment says specifically to use a for loop, and you've implemented a while loop.
I'll get you started:
for directory in $*; do
cd "$directory"
curentries=$(ls -1 | wc -l)
for entry in $(ls -1); do
...
done
done

Batch Renaming multiple files with different extensions Linux Script?

I would like to write a linux script that will move or copy all files with the same filename (but different extensions) to a new filename for all those files, while maintaining their different extensions. In other words:
if I start with a directory listing:
file1.txt, file1.jpg, file1.doc, file12.txt, file12.jpg, file12.doc
I would like to write a script to change all the filenames without changing the extensions. For the same example, choosing file2 as the new filename the result would be:
file2.txt, file2.jpg and file2.doc, file12.txt, file12.jpg, file12.doc
So the files whose filename do not match the current criteria will not be changed.
Best wishes,
George
Note: If there's file1.doc in variable i, expression ${i##*.} extracts extension i.e. doc in this case.
One line solution:
for i in file1.*; do mv "$i" "file2.${i##*.}"; done
Script:
#!/bin/sh
# first argument - basename of files to be moved
# second arguments - basename of destination files
if [ $# -ne 2 ]; then
echo "Two arguments required."
exit;
fi
for i in $1.*; do
if [ -e "$i" ]; then
mv "$i" "$2.${i##*.}"
echo "$i to $2.${i##*.}";
fi
done
The util-linux-ng package (most of linux flavours have it installed by default) has the command 'rename'. See man rename for use instructions. Using it your task can be done simply as that rename file1 file2 file1.*
To handle input files whose basenames contain special characters, I would modify plesiv's script to the following:
if [ $# -ne 2 ]; then
echo "Two arguments required."
exit;
fi
for i in "$1".*; do
if [ -e "$i" ]; then
mv "$i" "$2.${i##*.}"
echo "$i to $2.${i##*.}";
fi
done
Note the extra quotes around $1.

How to properly handle wildcard expansion in a bash shell script?

#!/bin/bash
hello()
{
SRC=$1
DEST=$2
for IP in `cat /opt/ankit/configs/machine.configs` ; do
echo $SRC | grep '*' > /dev/null
if test `echo $?` -eq 0 ; then
for STAR in $SRC ; do
echo -en "$IP"
echo -en "\n\t ARG1=$STAR ARG2=$2\n\n"
done
else
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
fi
done
}
hello $1 $2
The above is the shell script which I provide source (SRC) & desitnation (DEST) path. It worked fine when I did not put in a SRC path with wild card ''. When I run this shell script and give ''.pdf or '*'as follows:
root#ankit1:~/as_prac# ./test.sh /home/dev/Examples/*.pdf /ankit_test/as
I get the following output:
192.168.1.6
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/home/dev/Examples/case_howard_county_library.pdf
The DEST is /ankit_test/as but DEST also get manupulated due to '*'. The expected answer is
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/ankit_test/as
So, if you understand what I am trying to do, please help me out to solve this BUG.
I'll be grateful to you.
Thanks in advance!!!
I need to know exactly how I use '*.pdf' in my program one by one without disturbing DEST.
Your script needs more work.
Even after escaping the wildcard, you won't get your expected answer. You will get:
ARG1=/home/dev/Examples/*.pdf ARG2=/ankit__test/as
Try the following instead:
for IP in `cat /opt/ankit/configs/machine.configs`
do
for i in $SRC
do
echo -en "$IP"
echo -en "\n\t ARG1=$i ARG2=$DEST\n\n"
done
done
Run it like this:
root#ankit1:~/as_prac# ./test.sh "/home/dev/Examples/*.pdf" /ankit__test/as
The shell will expand wildcards unless you escape them, so for example if you have
$ ls
one.pdf two.pdf three.pdf
and run your script as
./test.sh *.pdf /ankit__test/as
it will be the same as
./test.sh one.pdf two.pdf three.pdf /ankit__test/as
which is not what you expect. Doing
./test.sh \*.pdf /ankit__test/as
should work.
If you can, change the order of the parameters passed to your shell script as follows:
./test.sh /ankit_test/as /home/dev/Examples/*.pdf
That would make your life a lot easier since the variable part moves to the end of the line. Then, the following script will do what you want:
#!/bin/bash
hello()
{
SRC=$1
DEST=$2
for IP in `cat /opt/ankit/configs/machine.configs` ; do
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
done
}
arg2=$1
shift
while [[ "$1" != "" ]] ; do
hello $1 $arg2
shift
done
You are also missing a final "done" to close your outer for loop.
OK, this appears to do what you want:
#!/bin/bash
hello() {
SRC=$1
DEST=$2
while read IP ; do
for FILE in $SRC; do
echo -e "$IP"
echo -e "\tARG1=$FILE ARG2=$DEST\n"
done
done < /tmp/machine.configs
}
hello "$1" $2
You still need to escape any wildcard characters when you invoke the script
The double quotes are necessary when you invoke the hello function, otherwise the mere fact of evaluating $1 causes the wildcard to be expanded, but we don't want that to happen until $SRC is assigned in the function
Here's what I came up with:
#!/bin/bash
hello()
{
# DEST will contain the last argument
eval DEST=\$$#
while [ $1 != $DEST ]; do
SRC=$1
for IP in `cat /opt/ankit/configs/machine.configs`; do
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
done
shift || break
done
}
hello $*
Instead of passing only two parameters to the hello() function, we'll pass in all the arguments that the script got.
Inside the hello() function, we first assign the final argument to the DEST var. Then we loop through all of the arguments, assigning each one to SRC, and run whatever commands we want using the SRC and DEST arguments. Note that you may want to put quotation marks around $SRC and $DEST in case they contain spaces. We stop looping when SRC is the same as DEST because that means we've hit the final argument (the destination).
For multiple input files using a wildcard such as *.txt, I found this to work perfectly, no escaping required. It should work just like a native bash app like "ls" or "rm." This was not documented just about anywhere so since I spent a better part of 3 days trying to figure it out I decided I should post it for future readers.
Directory contains the following files (output of ls)
file1.txt file2.txt file3.txt
Run script like
$ ./script.sh *.txt
Or even like
$ ./script.sh file{1..3}.txt
The script
#!/bin/bash
# store default IFS, we need to temporarily change this
sfi=$IFS
#set IFS to $'\n\' - new line
IFS=$'\n'
if [[ -z $# ]]
then
echo "Error: Missing required argument"
echo
exit 1
fi
# Put the file glob into an array
file=("$#")
# Now loop through them
for (( i=0 ; i < ${#file[*]} ; i++ ));
do
if [ -w ${file[$i]} ]; then
echo ${file[$i]} " writable"
else
echo ${file[$i]} " NOT writable"
fi
done
# Reset IFS to its default value
IFS=$sfi
The output
file1.txt writable
file2.txt writable
file3.txt writable
The key was switching the IFS (Internal Field Separator) temporarily. You have to be sure to store this before switching and then switch it back when you are done with it as demonstrated above.
Now you have a list of expanded files (with spaces escaped) in the file[] array which you can then loop through. I like this solution the best, easiest to program for and easiest for the users.
There's no need to spawn a shell to look at the $? variable, you can evaluate it directly.
It should just be:
if [ $? -eq 0 ]; then
You're running
./test.sh /home/dev/Examples/*.pdf /ankit_test/as
and your interactive shell is expanding the wildcard before the script gets it. You just need to quote the first argument when you launch it, as in
./test.sh "/home/dev/Examples/*.pdf" /ankit_test/as
and then, in your script, quote "$SRC" anywhere where you literally want the things with wildcards (ie, when you do echo $SRC, instead use echo "$SRC") and leave it unquoted when you want the wildcards expanded. Basically, always put quotes around things which might contain shell metacharacters unless you want the metacharacters interpreted. :)

Resources