How to loop in bash script? - bash

i have following lines in a bash script under Linux:
...
mkdir max15
mkdir max14
mkdir max13
mkdir max12
mkdir max11
mkdir max10
...
how is the syntax for putting them in a loop, so that i don't have to write the numbers (15,14..) ?

with bash, no need to use external commands like seq to generate numbers.
for i in {15..10}
do
mkdir "max${i}"
done
or simply
mkdir max{01..15} #from 1 to 15
mkdir max{10..15} #from 10 to 15
say if your numbers are generated dynamically, you can use C style for loop
start=10
end=15
for((i=$start;i<=$end;i++))
do
mkdir "max${i}"
done

No loop needed for this task:
mkdir max{15..10} max0{9..0}
... but if you need a loop construct, you can use one of:
for i in $(seq [ <start> [ <step> ]] <stop>) ; do
# you can use $i here
done
or
for i in {<start>..<stop>} ; do
# you can use $i here
done
or
for (( i=<start> ; i < stop ; i++ )) ; do
# you can use $i here
done
or
seq [ <start> [ <step> ]] <stop> | while read $i ; do
# you can use $i here
done
Note that this last one will not keep the value of $i outside of the loop, due to the | that starts a sub-shell

for a in `seq 10 15`; do mkdir max${a}; done
seq will generate numbers from 10 to 15.
EDIT: I was used to this structure since many years. However, when I observed the other answers, it is true, that the {START..STOP} is much better. Now I have to get used to create directories this much nicer way: mkdir max{10..15}.

Use a for-loop

for i in {1..15} ; do
mkdir max$i
done

Related

Why doesn't counting files with "for file in $0/*; let i=$i+1; done" work?

I'm new in ShellScripting and have the following script that i created based on a simpler one, i want to pass it an argument with the path to count files. Cannot find my logical mistake to make it work right, the output is always "1"
#!/bin/bash
i=0
for file in $0/*
do
let i=$i+1
done
echo $i
To execute the code i use
sh scriptname.sh /path/to/folder/to/count/files
$0 is the name with which your script was invoked (roughly, subject to several exceptions that aren't pertinent here). The first argument is $1, and so it's $1 that you want to use in your glob expression.
#!/bin/bash
i=0
for file in "$1"/*; do
i=$(( i + 1 )) ## $(( )) is POSIX-compliant arithmetic syntax; let is deprecated.
done
echo "$i"
That said, you can get this number more directly:
#!/bin/bash
shopt -s nullglob # allow globs to expand to an empty list
files=( "$1"/* ) # put list of files into an array
echo "${#files[#]}" # count the number of items in the array
...or even:
#!/bin/sh
set -- "$1"/* # override $# with the list of files matching the glob
if [ -e "$1" ] || [ -L "$1" ]; then # if $1 exists, then it had matches
echo "$#" # ...so emit their number.
else
echo 0 # otherwise, our result is 0.
fi
If you want to count the number of files in a directory, you can run something like this:
ls /path/to/folder/to/count/files | wc -l

bin bash bad interpreter

#! bin/bash
mkdir ~/folder
while [ $brojac -le 5]
do
mkdir ~/folder/zad"$brojac"
brojac = $(( brojac+1 ))
done
this is my shellscript,but when I want to run it in terminal, I receive this error
mint#mint ~ $ ./prvi.sh
bash: ./prvi.sh: bin/bash: bad interpreter: No such file or directory
mint#mint ~ $
It should be
#!/bin/bash
(first slash)
#!/bin/bash
mkdir ~/folder
brojac=0
while [ "$brojac" -le 5 ] # with [...], need to quote vars and spaces around [ and ]
do
mkdir ~/folder/zad"$brojac"
brojac=$(( brojac+1 )) # cannot have spaces around =
done
I would write:
for ((i=0; i<=5, i++)); do
mkdir -p ~/folder/zad$i
done
Or with an simple
mkdir -p ~/folder/zad{1..5}
if you want zad1, zad2 .. zad5
or
mkdir -p ~/folder/zad{,1..5}
if you want zad, zad1, zad2 .. zad5
Small errors in your script:
$brojac is unassigned, so your integer comparison fails. Assign it an initial value to fix.
'[' calls test, so you need spaces around opening and closing braces.
You can't have spaces around your equal sign when assigning a value.
Your script, updated:
#!/bin/bash
mkdir ~/folder
brojac=0
while [ $brojac -le 5 ]
do
mkdir ~/folder/zad"$brojac"
brojac=$(( brojac+1 ))
done

Using shell script, how do I put numbers in formats like 000?

I have large number of files which have file names in the format of XXX_name_YYY.out with YYY and YYY being numbers. I want to use a loop to move all files starting with XXX_name to a folder with the name 'XXX_name'. I am very new to shell scripting and only code a bit in C.
I would do something like this but the format of the numbers does not match the numbers in the file names.
c=1
while[c -le 100]
do
d=1
mkdir "$c"_name
while[d - le 100]
do
mv "$c"_name_"$d".out "$c"_name/"$c"_name_"$d".out
(( d++ ))
done
(( c++ ))
done
for FILE in [0-9][0-9][0-9]_name_[0-9][0-9][0-9].out; do
DIR=${FILE%_*.out}
[[ -d $DIR ]] || mkdir "$DIR" && echo mv "$FILE" $DIR/"
done
Remove echo when you're sure it works already.

How to delete all but two instances of a file?

I have a directory with similarly named files, in this pattern:
00002_930831_fa.ppm 00398_940422_fa.ppm 00714_960530_fa.ppm
00002_930831_fb.ppm 00398_940422_fb.ppm 00714_960530_fb.ppm
00002_931230_fa.ppm 00399_940422_fa.ppm 00714_960620_fa.ppm
00002_931230_fb.ppm 00399_940422_fb.ppm 00714_960620_fb.ppm
00002_940128_fa.ppm 00400_940422_fa.ppm 00715_941201_fa.ppm
00002_940128_fb.ppm 00400_940422_fb.ppm 00715_941201_fb.ppm
00002_940422_fa.ppm 00401_940422_fa.ppm 00715_941205_fa.ppm
00002_940422_fb.ppm 00401_940422_fb.ppm 00715_941205_fb.ppm
00002_940928_fa.ppm 00402_940422_fa.ppm 00716_941201_fa.ppm
00002_940928_fb.ppm 00402_940422_fb.ppm 00716_941201_fb.ppm
What I need to do is remove for example all but two instances of the 00002 sample (doesn't matter which ones), so that I'm left for example with 00002_930831_fa.ppm and 00002_930831_fb.ppm. The problem is I need this done for all samples, 00003, 00004 and so on. I need to be left with two files for each sample.
I've tried with find but I'm not sure how to frase my condition.
Can this be solved by simply piping commands or do I have to solve it with a bash script?
Just use head or tail to filter your filename list:
ls 00002_* | tail -n +3 | xargs rm
Create an array that contains all matching file names, then use the substring parameter expansion operator to pass all but the first two elements as arguments to rm.
while read -r sample; do
matching_files=( ${sample}_* )
# To make sure at least two files survive:
(( ${#matching_files[#]} > 2 )) && rm "${matching_files[#]:2}"
done < samples.txt
Using an associative array:
#!/bin/bash
[[ BASH_VERSINFO -ge 4 ]] || {
echo "You need Bash 4.0 or newer to run this script." >&2
exit 1
}
declare -A COUNTER=()
for A in *.ppm; do
IFS=_ read I __ <<< "$A"
(( ++COUNTER[$I] > 2 )) && rm "$A"
done
Simulation:
Skip 00002_930831_fa.ppm
Skip 00002_930831_fb.ppm
rm 00002_931230_fa.ppm
rm 00002_931230_fb.ppm
rm 00002_940128_fa.ppm
rm 00002_940128_fb.ppm
rm 00002_940422_fa.ppm
rm 00002_940422_fb.ppm
rm 00002_940928_fa.ppm
rm 00002_940928_fb.ppm
Skip 00398_940422_fa.ppm
Skip 00398_940422_fb.ppm
Skip 00399_940422_fa.ppm
Skip 00399_940422_fb.ppm
Skip 00400_940422_fa.ppm
Skip 00400_940422_fb.ppm
Skip 00401_940422_fa.ppm
Skip 00401_940422_fb.ppm
Skip 00402_940422_fa.ppm
Skip 00402_940422_fb.ppm
Skip 00714_960530_fa.ppm
Skip 00714_960530_fb.ppm
rm 00714_960620_fa.ppm
rm 00714_960620_fb.ppm
Skip 00715_941201_fa.ppm
Skip 00715_941201_fb.ppm
rm 00715_941205_fa.ppm
rm 00715_941205_fb.ppm
Skip 00716_941201_fa.ppm
Skip 00716_941201_fb.ppm
Note: Test it first on some dummy files.
Come to think of it:
IFS=_ read I __ <<< "$A"
Can just be
I=${A%%_*}
with bash version 4:
declare -A files
for f in *ppm; do
files[${f%%_*}]+="$f "
done
for i in "${!files[#]}"; do
set -- ${files[$i]}
shift 2
(($# > 0)) && echo rm $*
done
Remove echo if you're satisfied it's selecting the right files to delete.
Won't work if there are any filenames with whitespace.

Add arguements bash

Sup mates,
Building a "find" command in bash
Currently have it working when a directory is passed but if it isn't passed, running "find" is suppose to default to "find ./"
What occurs with my code is that my recursion function (a func that adds all items in its file tree) works with a for loop that uses $#
recurse(){
oldIFS=$IFS
IFS=$'\n'
for f in $#
do
list="`echo -e "$list\n$PWD/${f}"`"
if [[ -d "${f}" ]]
then
cd "${f}"
recurse $(ls -1 ".")
cd ..
fi
done
IFS=$oldIFS
}
So is there a way to add a command line argument so i keep the code the same
or
How do i create a variable that holds $# so i can use that in the for loop above
and then i can just set that variable to "./" if i detect $# == 0
Use an array, to match the array in $#.
args=("$#")
if (( ${#args[#]} == 0 ))
then
args=(./)
fi
for f in "${args[#]}"
do
...
done

Resources