I was doing this little script in which the first argument must be a path to an existing directory and the second any other thing.
Each object in the path indicated in the first argument must be renamed so that the new
name is the original that was added as a prefix to the character string passed as the second argument. Example, for the string "hello", the object OBJECT1 is renamed hello.OBJECT1 and so on
Additionally, if an object with the new name is already present, a message is shown by a standard error output and the operation is not carried out continuing with the next object.
I have the following done:
#! /bin/bash
if [ "$#" != 2 ]; then
exit 1
else
echo "$2"
if [ -d "$1" ]; then
echo "directory"
for i in $(ls "$1")
do
for j in $(ls "$1")
do
echo "$i"
if [ "$j" = "$2"."$i" ]; then
exit 1
else
mv -v "$i" "$2"."$i"
echo "$2"."$i"
fi
done
done
else
echo "no"
fi
fi
I am having problems if I run the script from another file other than the one I want to do it, for example if I am in /home/pp and I want the changes to be made in /home/pp/rr, since that is the only way It does in the current.
I tried to change the ls to catch the whole route with
ls -R | sed "s;^;pwd;" but the route catches me badly.
Using find you can't because it puts me in front of the path and doesn't leave the file
Then another question, to verify that that object that is going to create new is not inside, when doing it with two for I get bash errors for all files and not just for coincidences
I'm starting with this scripting, so it has to be a very simple solution thing
An obvious answer to your question would be to put a cd "$2 in the script to make it work. However, there are some opportunities in this script for improvement.
#! /bin/bash
if [ "$#" != 2 ]; then
You might put an error message here, for example, echo "Usage: $0 dir prefix" or even a more elaborate help text.
exit 1
else
echo $2
Please quote, as in echo "$2".
if [ -d $1 ]; then
Here, the quotes are important. Suppose that your directory name has a space in it; then this if would fail with bash: [: a: binary operator expected. So, put quotes around the $1: if [ -d "$1" ]; then
echo "directory"
This is where you could insert the cd "$1".
for i in $(ls $1)
do
It is almost always a bad idea to parse the output of ls. Once again, this for-loop will fail if a file name has a space in it. A possible improvement would be for i in "$1"/* ; do.
for j in $(ls $1)
do
echo $i
if [ $j = $2.$i ]; then
exit 1
else
The logic of this section seems to be: if a file with the prefix exists, then exit instead of overwriting. It is always a good idea to tell why the script fails; an echo before the exit 1 will be helpful.
The question is why you use the second loop? a simple if [ -f "$2.$i ] ; then would do the same, but without the second loop. And it will therefore be faster.
mv -v $i $2.$i
echo $2.$i
Once again: use quotes!
fi
done
done
else
echo "no"
fi
fi
So, with all the remarks, you should be able to improve your script. As tripleee said in his comment, running shellcheck would have provided you with most of the comment above. But he also mentioned basename, which would be useful here.
With all that, this is how I would do it. Some changes you will probably only appreciate in a few months time when you need some changes to the script and try to remember what the logic was that you had in the past.
#!/bin/bash
if [ "$#" != 2 ]; then
echo "Usage: $0 directory prefix" >&2
echo "Put a prefix to all the files in a directory." >&2
exit 1
else
directory="$1"
prefix="$2"
if [ -d "$directory" ]; then
for f in "$directory"/* ; do
base=$(basename "$f")
if [ -f "Sdirectory/$prefix.$base" ] ; then
echo "This would overwrite $prefix.$base; exiting" >&2
exit 1
else
mv -v "$directory/$base" "$directory/$prefix.$base"
fi
done
else
echo "$directory is not a directory" >&2
fi
fi
Related
I want to write the bash script which would accept 4 parameters: name of file, name of directory, and two strings.
If there is a mistake (if first parameter is not a file or second is not a directory) then string which is a third parameter should be printed else file should be copied to directory and string which is a fourth parameter should be printed. I don't why the compiler reports mistake in line 3 with then.
#!/bin/bash
if [-f $1]; then
if[-d $2] ; then
cp $1 / $2
echo $4
fi
done
else
echo $3
exit 1
fi
If you are having problems, paste your code in at https://www.shellcheck.net/
Fix each issue, then get the report again.
The result:
#!/bin/bash
if [ -f "$1" ]; then
if [ -d "$2" ] ; then
cp "$1" / "$2"
echo "$4"
fi
else
echo "$3"
exit 1
fi
I still think you are likely to have an issue at line 4 though, when it tries to copy the root directory into arg 2 without -r. I think what you meant was just
cp "$1" "$2"
Also, you have action for the case that someone passes a valid file as $1 but a non-directory as $2. The program will just exit silently and do nothing.
I have to write a shell script that creates a file containing the name of each text files from a folder (given as parameter) and it's subfolders that contain words longer than n characters (read n from keyboard).
I wrote the following code so far :
#!/bin/bash
Verifies if the first given parameter is a folder:
if [ ! -d $1 ]
then echo $1 is not a directory\!
exit 1
fi
Reading n
echo -n "Give the number n: "
read n
echo "You entered: $n"
Destination where to write the name of the files:
destinatie="destinatie"
the actual part that i think it makes me problems:
nr=0;
#while read line;
#do
for fisier in `find $1 -type f`
do
counter=0
for word in $(<$fisier);
do
file=`basename "$fisier"`
length=`expr length $word`
echo "$length"
if [ $length -gt $n ];
then counter=$(($counter+1))
fi
done
if [ $counter -gt $nr ];
then echo "$file" >> $destinatie
fi
done
break
done
exit
The script works but it does a few more steps that i don't need.It seems like it reads some files more than 1 time. If anyone can help me please?
Does this help?
egrep -lr "\w{$n,}" $1/* >$destinatie
Some explanation:
\w means: a character that words consist of
{$n,} means: number of consecutive characters is at least $n
Option -l lists files and does not print the grepped text and -r performs a recursive scan on your directory in $1
Edit:
a bit more complete version around the egrep command:
#!/bin/bash
die() { echo "$#" 1>&2 ; exit 1; }
[ -z "$1" ] && die "which directory to scan?"
dir="$1"
[ -d "$dir" ] || die "$dir isn't a directory"
echo -n "Give the number n: "
read n
echo "You entered: $n"
[ $n -le 0 ] && die "the number should be > 0"
destinatie="destinatie"
egrep -lr "\w{$n,}" "$dir"/* | while read f; do basename "$f"; done >$destinatie
This code has syntax errors, probably leftovers from your commented-out while loop: It would be best to remove the last 3 lines: done causes the error, break and exit are unnecessary as there is nothing to break out from and the program always terminates at its end.
The program appears to output files multiple times because you just append to $destinatie. You could simply delete that file when you start:
rm "$destinatie"
You echo the numbers to stdout (echo "$length") and the file names to $destinatie (echo "$file" >> $destinatie). I do not know if that is intentional.
I found the problem.The problem was the directory in which i was searching.Because i worked on the files from the direcotry and modified them , it seems that there remained some files which were not displayed in file explorer but the script would find them.i created another directory and i gived it as parameter and it works. Thank you for your answers
.
I am making a bash script that you have to give 2 files or more as arguments.
I want to test if the given files exist. I'm using a while loop because I don't know how many files are given. The problem is that the if statement sees the $t as a number and not as the positional parameter $number. Does somebody have a solution?
t=1
max=$#
while [ $t -le $max ]; do
if [ ! -f $t ]; then
echo "findmagic.sh: $t is not a regular file"
echo "Usage: findmagic.sh file file ...."
exit
fi
t=`expr $t + 1`
done
You can do it with the bash Special parameter # in this way:
script_name=${0##*/}
for t in "$#"; do
if [ ! -f "$t" ]; then
echo "$script_name: $t is not a regular file"
echo "Usage: $script_name file file ...."
exit 1
fi
done
With "$#" you are expanding the positional parameters, starting from one as separate words (your arguments).
Besides, remember to provide a meaningful exit status (e.g. exit 1 instead of exit alone). If not provided, the exit status is that of the last command executed (echo in your case, which succes, so you're exiting with 0).
And for last, instead of write the script name (findmagic.sh in your case), you can set a variable at the beginning in your script:
script_name=${0##*/}
and then use $script_name when necessary. In this way you don't need to update your script if it changes its name.
I'm writing a simple recursive ls program in bash (which I'm very not experienced at, so feel free to be brutal).
The program is supposed to print out each file (possibly directory) on a separate line, and each time a new directory is entered, the output is shifted over by 4 spaces, to give it a tree-like output.
Currently, it doesn't print out files with spaces correctly, and it doesn't put a forward slash after directories. (More details below.)
Code
recls () {
# store current working directory
# issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
cwd=$PWD
# get absolute path of arg
argdir=`getAbsolutePath "$#"`
# check if it exists
if [ ! -e $argdir ]; then
echo "$argdir does not exist"
return 1
fi
echo "$argdir exists"
# check if it's a directory
if [ ! -d $argdir ]; then
echo "$argdir is not a directory"
return 2
fi
echo "$argdir is a directory"
tab=""
recls_internal $argdir
return 0
}
recls_internal () {
for file in $#; do
echo -n "$tab${file##/*/}"
if [ -d $file ]; then
# print forward slash to show it's a directory
echo "/"
savedtab=$tab
tab="$tab "
myls_internal $file/*
tab=$savedtab
else
# if not a directory, print a new line
echo ""
fi
done
}
getAbsolutePath () {
if [ -z ${1##/*} ]; then
echo "$1"
else
echo "$cwd/$1"
fi
}
Output
The script is contained in a folder called bash-practice. When I do recls ., I get the following output:
./
myls.sh
myls.sh~
recdir.sh
recls.sh
recls.sh~
sample
document.txt
sample-folder
sample-stuff
test-12.txt
test-1.txt
test-2.txt
sort-test.txt
sort-text-copy.txt
test-5-19-14-1
The Problem
As you can see, the indentation is working properly but there are two problems:
1) The file sample document.txt is spread across two lines, because it has a space in it.
2) Every directory should have a forward slash in front of it, but for some reason that only works on the very first one.
Attempted Solution
In order to fix (1), I tried saving the internal file separator and replacing it with a newline character like so:
...
tab=""
savedIFS=$IFS
IFS="\n"
recls_internal $argdir
IFS=$savedIFS
return 0
But this did not work at all. It didn't even display more than the first folder. Clearly my understanding of things is not correct.
As for (2), I don't see any reason why it shouldn't be working as intended.
Conclusion
bash is difficult for me as it seems to have more unusual syntax than most other programming languages (being a shell scripting language), so I would appreciate any insights into my mistakes, as well as a solution.
Update #1
I went to the site http://www.shellcheck.com that mklement0 suggested, and its hints were basically all to double quote things more. When I double quoted "$#", the program correctly printed the file sample document.txt, but then directly after that, it gave me a "binary operator expected" error. Here is a print out of what it looks like now:
Update #2 [problem solved?]
OK, it turns out that I had a typo which was causing it to default to an earlier version of my function called myls_internal when it recursed. This earlier version didn't mark directories with a forward slash. The error message in the "Update" section was also fixed. I changed the line
myls_internal "$file/*"
to
recls_internal $file/*
and now it seems to work properly. If anyone is in the middle of writing an answer, I still appreciate your insights as I don't really understand the mechanics of how quoting "$#" fixed the spacing issue.
Fixed code:
recls () {
# store current working directory
# issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
cwd=$PWD
# get absolute path of arg
argdir=$(getAbsolutePath "$#")
# check if it exists
if [ ! -e $argdir ]; then
echo "$argdir does not exist"
return 1
fi
echo "$argdir exists"
# check if it's a directory
if [ ! -d $argdir ]; then
echo "$argdir is not a directory"
return 2
fi
echo "$argdir is a directory"
tab=""
recls_internal $argdir
return 0
}
recls_internal () {
for file in "$#"; do
echo -n "$tab${file##/*/}"
if [ -d "$file" ]; then
# print forward slash to show it's a directory
echo "/"
savedtab=$tab
tab="$tab "
recls_internal $file/*
tab=$savedtab
else
# if not a directory, print a new line
echo ""
fi
done
}
getAbsolutePath () {
if [ -z ${1##/*} ]; then
echo "$1"
else
echo "$cwd/$1"
fi
}
Fixed output:
Update #3
The line
recls_internal $file/*
should instead be
recls_internal "$file"/*
which handles directories with spaces in them correctly. Otherwise, a folder such as cs 350 containing Homework1.pdf and Homework2.pdf will expand to
cs 350/Homework1.pdf 350/Homework2.pdf
when it should be
cs 350/Homework1.pdf cs 350/Homework2.pdf
I think? I don't really get the finer details of what's going on, but that seemed to fix it.
To illustrate the difference between "$#" and $#, let us consider the two following functions:
f() { for i in $#; do echo $i; done; }
g() { for i in "$#"; do echo $i; done; }
When calling these function with the parameters a "b c" "d e" the result will be
function f
f a "b c" "d e"
a
b
c
d
e
function g
g a "b c" "d e"
a
b c
d e
So when "$#" is within double quotes, the expansion keeps each parameter in a separate word (even if the parameter contains one or more space).
When $# (without double quotes) is expanded, a parameter with a space will be considered as two words.
In your script, you need also to surround argdir and file with double quotes. It is useful when the name of a directory or a file contains space so the name will be considered as a single value. Below your script modified.
#! /bin/bash -u
recls () {
# store current working directory
# issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
cwd=$PWD
# get absolute path of arg
argdir=`getAbsolutePath "$#"`
# check if it exists
if [ ! -e "$argdir" ]; then
echo "$argdir does not exist"
return 1
fi
echo "$argdir exists"
# check if it's a directory
if [ ! -d "$argdir" ]; then
echo "$argdir is not a directory"
return 2
fi
echo "$argdir is a directory"
tab=""
recls_internal "$argdir"
return 0
}
recls_internal () {
for file in "$#"; do
echo -n "$tab${file##/*/}"
if [ -d "$file" ]; then
# print forward slash to show it's a directory
echo "/"
savedtab=$tab
tab="$tab "
recls_internal "$file"/*
tab=$savedtab
else
# if not a directory, print a new line
echo ""
fi
done
}
getAbsolutePath () {
if [ -z ${1##/*} ]; then
echo "$1"
else
echo "$cwd/$1"
fi
}
This is just a simple problem but I don't understand why I got an error here. This is just a for loop inside an if statement.
This is my code:
#!/bin/bash
if (!( -f $argv[1])) then
echo "Argv must be text file";
else if ($#argv != 1) then
echo "Max argument is 1";
else if (-f $argv[1]) then
for i in `cut -d ',' -f2 $argv[1]`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
Error is in line 16, which is the line after fi, that is a blank line .....
Can someone please explain why i have this error ????
many, many errors.
If I try to stay close to your example code:
#!/bin/sh
if [ ! -f "${1}" ]
then
echo "Argv must be text file";
else if [ "${#}" -ne 1 ]
then
echo "Max argument is 1";
else if [ -f "${1}" ]
then
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
fi
fi
fi
another way, exiting each time the condition is not met :
#!/bin/sh
[ "${#}" -ne 1 ] && { echo "There should be 1 (and only 1) argument" ; exit 1 ; }
[ ! -f "${1}" ] && { echo "Argv must be a file." ; exit 1 ; }
[ -f "${1}" ] && {
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
}
#!/usr/local/bin/bash -x
if [ ! -f "${1}" ]
then
echo "Argument must be a text file."
else
while-loop-script "${1}"
fi
I have broken this up, because I personally consider it extremely bad form to nest one function inside another; or truthfully to even have more than one function in the same file. I don't care about file size, either; I've got several scripts which are 300-500 bytes long. I'm learning FORTH; fractalism in that sense is a virtue.
# while-loop-script
while read line
do
IFS="#"
ping -c 3 "${line}"
IFS=" "
done < "${1}"
Don't use cat in order to feed individual file lines to a script; it will always fail, and bash will try and execute the output as a literal command. I thought that sed printing would work, and it often does, but for some reason it very often substitutes spaces for newlines, which is extremely annoying as well.
The only absolutely bulletproof method of feeding a line to a script that I know of, which will preserve all space and formatting, is to use while-read loops, rather than substituted for cat or for sed loops, as mentioned.
Something else which you will need to do, in order to be sure about preserving whitespace, is to set the internal field seperator (IFS) to something that you know your file will not contain, and then resetting it back to whitespace at the end of the loop.
For every opening if, you must have a corresponding closing fi. This is also true for else if. Better use elif instead
if test ! -f "$1"; then
echo "Argv must be text file";
elif test $# != 1; then
echo "Max argument is 1";
elif test -f "$1"; then
for i in `cut -d ',' -f2 "$1"`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
There's also no argv variable. If you want to access the command line arguments, you must use $1, $2, ...
Next point is $#argv, this evaluates to $# (number of command line args) and argv. This looks a lot like perl.
Furthermore, testing is done with either test ... or [ ... ], not ( ... )
And finally, you should enclose at least your command line arguments in double quotes "$1". If you don't and there is no command line argument, you have for example
test ! -f
instead of
test ! -f ""
This lets the test fail and go on to the second if, instead of echoing the proper message.