How to use a for loop to create folders in bash - bash

I want to create a directory in which there is a bunch of text files using a for loop. Here is my code:
#!/bin/bash
echo "enter the nums: "
read num1 num2
for (( counter=0; counter<$num2; counter++ ))
do
if [ $num1 -lt 10 ] && [ $num2 -lt 10 ];
then
mkdir $num1 && touch $num1/$num1$num2.txt
echo "$num1""$num2" > $num1/$num1$num2.txt
else
echo "you weren't supposed to do that"
fi
done
What I want to happen if for example the user entered: "2 9"
Make a directory called 2
In it make text files called 290.txt, 291.txt, 292.txt... up till 299.txt.
Instead, what happens right now is it makes the directory and gives an error that the directory already exists. I don't know the next step, please help.

The biggest problem here is that you're doing things inside the loop that really only should be done once. Specifically, the error you're getting is because it tries to create the directory every time through the loop, but you can only create it once. Also, if the user enters too large a number, it'll print multiple error messages (e.g. if num2 is entered as 500, it'll print 500 error messages). You need to do both the error check and creating the directory once, before the loop.
A second problem is that you don't add $counter to the filename, so if the user enters "2 9", it'll create a file named 29.txt nine times.
You also have some more minor issues: in general, error messages should be printed to standard error instead of standard output (you can redirect them with >&2), and if there's an error the script should exit with a nonzero status. Also, you should (almost always) put double-quotes around variable references, to avoid weird results if the variables are blank or contain whitespace or some other things. You also don't need to touch files before writing into them (using > somefile will create the file if it doesn't exist).
With these things fixed (and some stylistic tweaks), here's what I get:
#!/bin/bash
echo "enter the nums: "
read num1 num2
if ! [ "$num1" -lt 10 ] || ! [ "$num2" -lt 10 ]; then
echo "you weren't supposed to do that" >&2 # message send to stderr
exit 1 # exit with error status
fi
mkdir "$num1" || exit $? # if mkdir fails, exit with its error status
for (( counter=0; counter<$num2; counter++ )); do
echo "${num1}${num2}" > "${num1}/${num1}${num2}${counter}.txt"
done
BTW, the ! [ "$num1" -lt 10 ] tests may look a little weird; why not just use [ "$num" -ge 10 ]? I did it that way in case $num1 and/or $num2 isn't a valid number, in which case both -lt and -ge tests would fail; using a negated test makes that an error rather than a success.

I'm not fluent in bash or anything, but it looks like mkdir $num1 is called on every loop. Find out first if the directory exists.

Here you are! change the if and for statement parent and child:
#!/bin/bash
echo "enter the nums: "
read num1 num2
if [ $num1 -lt 10 ] && [ $num2 -lt 10 ]; then
mkdir $num1
for i in $(seq 0 $num2); do
touch $num1/$num1$num2$i.txt
echo "$num1""$num2""$i" > $num1/$num1$num2$i.txt
done
else
echo "you weren't supposed to do that"
fi

Related

Bash Script stuck looping

I'm trying to write a script that runs another script which fails rarely until it fails.
Here is the script that fails rarely:
#!/usr/bin/bash
n=$(( RANDOM % 100 ))
if (( n == 42 )) ; then
echo "$n Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "$n Everything went according to plan"
Here is the script that should run the previous script until it fails:
#!/usr/bin/bash
script_path="/tmp/missing/l2q3.sh"
found=0
counter=0
while (( $found == 0 )); do
output=(bash $script_path)
if (( $output == 42 Something went wrong )); then
found=1
fi
((counter++))
if (( $found == 1 )); then
echo "Number 42 was found after $counter tries"
fi
done
when I try running the second script I get stuck in an infinite loop saying there is a syntax error on line 11 and that something is wrong with 42 Something went wrong. I've tried with "42 Something went wrong" aswell and still stuck in a loop.
The form (( )) is arithemetic only, so you cannot test a string inside.
To test a string, you have to use the [[ ]] version:
[[ $output == "42 Something went wrong" ]] && echo ok
ok
You can use the program execution as the test for a while/until/if (etc.)
Assuming your script returns a valid 0 error code on success, and nonzero on any other circumstance, then -
$: cat tst
#!/bin/bash
trap 'rm -fr $tmp' EXIT
tmp=$(mktemp)
while /tmp/missing/l2q3.sh >$tmp; do let ++ctr; done
grep -q "^42 Something went wrong" $tmp &&
echo "Number 42 was found after $ctr tries"
In use:
$: ./tst
The error was using magic numbers
Number 42 was found after 229 tries
here are 3 steps to move forward.
add a return value at the end of the first script
exit 0
make your first script has executable rights
$ chmod a+x /tmp/missing/12q3.sh
instead of while loop you may use until, which would run until it returns success i.e. 0
until /tmp/missing/l2q3.sh; do ((counter++)) done
for other if statements please use square brackets [ single or double [[.

Shell script with absolute path and control errors

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

bash - how to put $RANDOM into value?

newbie to bash:
basically I want to compare the result of $RANDOM to another value which is given by the user through 'read'
code for more info:
echo $RANDOM % 10 + 1 | bc
basically I want an if statement as well to see if the result of that $RANDOM value is equal to something that the user typed in e.g.:
if [ [$RANDOM VALUE] is same as $readinput
#readinput is the thing that was typed before
then
echo "well done you guessed it"
fi
something along the lines of that!!
to summarise
how do i make it so that i can compare a read input value to echo "$RANDOM % 10 + 1 | bc"
think of the program I am making as 'GUESS THE NUMBER!'
all help VERY MUCH APPRECIATED :)
There's no need for bc here -- since you're dealing in integers, native math will do.
printf 'Guess a number: '; read readinput
target=$(( (RANDOM % 10) + 1 )) ## or, less efficiently, target=$(bc <<<"$RANDOM % 10 + 1")
if [ "$readinput" = "$target" ]; then
echo "You correctly guessed $target"
else
echo "Sorry -- you guessed $readinput, but the real value is $target"
fi
The important thing, though, is the test command -- also named [.
test "$readinput" = "$target"
...is exactly the same as...
[ "$readinput" = "$target" ]
...which does the work of comparing two values and exiting with an exit status of 0 (which if will treat as true) should they match, or a nonzero exit status (which if will treat as false) otherwise.
The short answer is to use command substitution to store your randomly generated value, then ask the user for a guess, then compare the two. Here's a very simple example:
#/bin/bash
#Store the random number for comparison later using command substitution IE: $(command) or `command`
random=$(echo "$RANDOM % 10 + 1" | bc)
#Ask the user for their guess and store in variable user_guess
read -r -p "Enter your guess: " user_guess
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
else
echo "sorry, try again"
fi
Perhaps a more robust guessing program would be embedded in a loop so that it would keep asking the user until they got the correct answer. Also you should probably check that the user entered a whole number.
#!/bin/bash
keep_guessing=1
while [ "$keep_guessing" -eq 1 ]; do
#Ask the user for their guess and check that it is a whole number, if not start the loop over.
read -r -p "Enter your guess: " user_guess
[[ ! $user_guess =~ ^[0-9]+$ ]] && { echo "Please enter a number"; continue; }
#Store the random number for comparison later
random=$(echo "$RANDOM % 10 + 1" | bc)
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
keep_guessing=0
else
echo "sorry, try again"
fi
done

integer expected error...blank line?

I'm getting a "integer expression expected error" when doing the following:
NM=$(<file)
if test $NM -gt 0
then
echo "workflow 1 would follow here"
else
if test $NM -lt 0
then
echo "workflow 2 would go after this"
else
echo "something else"
fi
fi
The file from which I am getting $NM assigned only contains one number (always an integer). This file is the output of a gmtmath operation.
I've noticed that if I open that file it has an extra line below the line containing the number and, if I manually delete that empty line my loop works and I don't get the error. However, I've tried to use sed in various ways to automatically delete empty lines and it deletes the entire content of the file, including the number. Any ideas??
After reading some comments, I'm wondering if you are getting some unprintable characters. I've tested this by putting in a \r before the \n, was finally able to reproduce. Here's a cleaned up version of your code, using tr to remove some extra characters.
NM=$(tr -cd '[:graph:]' <file)
if [[ $NM -gt 0 ]]; then
echo "workflow 1 would follow here"
elif [[ $NM -lt 0 ]]; then
echo "workflow 2 would go after this"
else
echo "something else"
fi
I could have used other classes other than [:graph:]. I just end up using [:graph:] often, but to each their own.
You want to test ${#NM} is greater than zero (e.g. the number of characters in $NM). Currently you are testing the contents of the string against 0 and bash is telling you it needs a number. The same applies for each of your tests where you need a number.
If you want to test whether the word "$NM" is greater than 0 or less than 0 use if ((NM > 0)); then ... or if ((NM < 0)); then ....
You can also use the older test expression if [ "$NM" -gt 0 ]; then... or if test "$NM" -gt 0 ; then... just be aware you will generate your error if "$NM" cannot be interpreted as a number. (you can redirect stderr to avoid that, but that isn't wise here).

For loop to find out if directory exists in unix

I want to use a script that checks whether a list of directories exists or not and at the same time it should print some custom message that I am sending.
For example:
I have a script that validates if directory exists or not:
**check.sh**
for i in $*
if [ -d "$i" ]; then
echo Found <msg-i> directory.
else
echo <msg-i> directory not found.
Now I want to call this script like this:
./check.sh $DIR1 msg1 $Dir2 msg2 $Dir3 msg3
So if DIR1 doesn't exist then I want to display message as "msg1 directory not found", similarly for DIR2 I want to show "msg2 directory not found". Here msg1 and msg2 are something I want to pass as string. How to achieve this? I am using bash shell.
Try this:
while [ -n "$1" ]
do
dir="$1"
msg="$2"
if [ -d "$dir" ]; then
echo "$msg dir FOUND"
else
echo "$msg dir NOT FOUND"
fi
shift 2
done
shift <n> command simply shifts left positional parameters passed to the script of n positions.
For example if you call a script with:
./myscript 1 2 3 4
$1 is "1" and $2 is "2"
but if you shift 2 then $1 is "3" and $2 is "4".
In this way the loop consumes 2 parameters per cycle until $1 parameter is an empty string ( -n "$1").
while condition can be written more elegantly as:
while (( $# ))
obtaining the same result.
You can also check for the second parameter (while [ -n "$2" ]) but this changes the behavior when user provides an odd number of parameters:
in the first case last directory will be checked but you'll have a strange message because $msg il empty
il the second case you'll not have strange messages, but last directory will silently not be checked
Better test parameters at the beginning:
if (( $# % 2 ))
then
echo "Provide an even number of parameters"
exit 1
fi
Chepner Says:
The while condition can simply be (( $# )) (test if the number of positional parameters is non-zero).
Chaitanya Says:
Hi Chepner, thanks for providing alternate solution, can you please tell me how the while condition should actually look like in order to use $# , I tried different ways but it is not working for me.
Here's a quick sample:
while (( $# ))
do
dir=$1
msg=$2
shift 2
[...]
done
The while (( $# )) will be true as long as there are any command line arguments. Doing the shift twice removes arguments from the list. When no more arguments, the while loop ends.
#Zac has the correct answer.
One tip for the message: use a printf format string:
./check.sh dir1 "can't locate %s directory"
and in the script:
if [[ ! -d "$dir" ]]; then
printf "$msg" "$dir"

Resources