bin bash bad interpreter - bash

#! 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

Related

Script is not glob-expanding, but works fine when running the culprit as a minimalistic example

I've been trying for hours on this problem, and cannot set it straight.
This minimal script works as it should:
#!/bin/bash
wipe_thumbs=1
if (( wipe_thumbs )); then
src_dir=$1
thumbs="$src_dir"/*/t1*.jpg
echo $thumbs
fi
Invoke with ./script workdir and a lot of filenames starting with t1* in all the sub-dirs of workdir are shown.
When putting the above if-case in the bigger script, the globbing is not executed:
SRC: -- workdir/ --
THUMBS: -- workdir//*/t1*.jpg --
ls: cannot access workdir//*/t1*.jpg: No such file or directory
The only difference with the big script and the minimal script is that the big script has a path-validator and getopts-extractor. This code is immediately above the if-case:
#!/bin/bash
OPTIONS=":ts:d:"
src_dir=""
dest_dir=""
wipe_thumbs=0
while getopts $OPTIONS opt ; do
case "$opt" in
t) wipe_thumbs=1
;;
esac
done
shift $((OPTIND - 1))
src_dir="$1"
dest_dir="${2:-${src_dir%/*}.WORK}"
# Validate source
echo -n "Validating source..."
if [[ -z "$src_dir" ]]; then
echo "Can't do anything without a source-dir."
exit
else
if [[ ! -d "$src_dir" ]]; then
echo "\"$src_dir\" is really not a directory."
exit
fi
fi
echo "done"
# Validate dest
echo -n "Validating destination..."
if [[ ! -d "$dest_dir" ]]; then
mkdir "$dest_dir"
(( $? > 0 )) && exit
else
if [[ ! -w "$dest_dir" ]]; then
echo "Can't write into the specified destination-dir."
exit
fi
fi
echo "done"
# Move out the files into extension-named directories
echo -n "Moving files..."
if (( wipe_thumbs )); then
thumbs="$src_dir"/*/t1*.jpg # not expanded
echo DEBUG THUMBS: -- "$thumbs" --
n_thumbs=$(ls "$thumbs" | wc -l)
rm "$thumbs"
fi
...rest of script, never reached due to error...
Can anyone shed some lights on this? Why is the glob not expanded in the big script, but working fine in the minimalistic test script?
EDIT: Added the complete if-case.
The problem is that wildcards aren't expanded in assignment statements (e.g. thumbs="$src_dir"/*/t1*.jpg), but are expanded when variables are used without double-quotes. Here's an interactive example:
$ src_dir=workdir
$ thumbs="$src_dir"/*/t1*.jpg
$ echo $thumbs # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg workdir/sub1/t1-2.jpg workdir/sub2/t1-1.jpg workdir/sub2/t1-2.jpg
$ echo "$thumbs" # Double-quotes, wildcards printed literally
workdir/*/t1*.jpg
$ ls $thumbs # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg workdir/sub2/t1-1.jpg
workdir/sub1/t1-2.jpg workdir/sub2/t1-2.jpg
$ ls "$thumbs" # Double-quotes, wildcards treated as literal parts of filename
ls: workdir/*/t1*.jpg: No such file or directory
...so the quick-n-easy fix is to remove the double-quotes from the ls and rm commands. But this isn't safe, as it'll also cause parsing problems if $src_dir contains any whitespace or wildcard characters (this may not be an issue for you, but I'm used to OS X where spaces in filenames are everywhere, and I've learned to be careful about these things). The best way to do this is to store the list of thumb files as an array:
$ src="work dir"
$ thumbs=("$src_dir"/*/t1*.jpg) # No double-quotes protect $src_dir, but not the wildcard portions
$ echo "${thumbs[#]}" # The "${array[#]}" idiom expands each array element as a separate word
work dir/sub1/t1-1.jpg work dir/sub1/t1-2.jpg work dir/sub2/t1-1.jpg work dir/sub2/t1-2.jpg
$ ls "${thumbs[#]}"
work dir/sub1/t1-1.jpg work dir/sub2/t1-1.jpg
work dir/sub1/t1-2.jpg work dir/sub2/t1-2.jpg
You might also want to set nullglob in case there aren't any matches (so it'll expand to a zero-length array).
In your script, this'd come out something like this:
if (( wipe_thumbs )); then
shopt -s nullglob
thumbs=("$src_dir"/*/t1*.jpg) # expanded as array elements
shopt -u nullglob # back to "normal" to avoid unexpected behavior later
printf 'DEBUG THUMBS: --'
printf ' "%s"' "${thumbs[#]}"
printf ' --\n'
# n_thumbs=$(ls "${thumbs[#]}" | wc -l) # wrong way to do this...
n_thumbs=${#thumbs[#]} # better...
if (( n_thumbs == 0 )); then
echo "No thumb files found" >&2
exit
fi
rm "${thumbs[#]}"
fi

Execute command in bash for a set of corresponding file names

I have a directory with lots of config files in it, they look like this:
us-sfo-building1.foo us-sfo-building1.bar
mx-mex-building15.foo mx-mex-building15.bar
Now, I want to execute a script which takes us-sfo-building1.foo and us-sfo-building1.bar as input parameters.
I want basically this: ./script $.foo $.bar but I have to make sure that I always have the matching pair of foo and bar, otherwise the script complains. I tried this, but it did not work as expected:
#/bin/bash
for x in "*.foo*; do
x=${x%.foo}
if [ -e "$x.bar" ]; then
./script "$x.foo" "$x.bar"
fi
done
Any idea on how to solve this or where my mistake is?
for x in "*.foo*; do doesn't need a ".
Fix:
#/bin/bash
for x in *.foo; do
x=${x%.foo}
if [ -e "$x.bar" ]; then
./script "$x.foo" "$x.bar"
fi
done
And a suggestion (includes preferred use of [[ ]]):
#/bin/bash
for x in *.foo; do
y=${x%.foo}.bar
[[ -e $y ]] && ./script "$x" "$y"
done

Bash script trouble interpretting input

I wrote a bash script that uploads a file on my home server. It gets activated from a folder action script using applescript. The setup is the folder on my desktop is called place_on_server. Its supposed to have an internal file structure exactly like the folder I want to write to: /var/www/media/
usage goes something like this:
if directory etc added to place_on_server: ./upload DIR etc
if directory of directory: etc/movies ./upload DIR etc movies //and so on
if file to place_on_server: ./upload F file.txt
if file in file in place_on_server ./upload F etc file.txt //and so on
for creating a directory its supposed to execute a command like:
ssh root#192.168.1.1<<EOF
cd /var/www/media/wherever
mkdir newdirectory
EOF
and for file placement:
rsync -rsh='ssh -p22' file root#192.168.1.1:/var/www/media/wherever
script:
#!/bin/bash
addr=$(ifconfig -a | ./test)
if ($# -le "1")
then
exit
elif ($1 -eq "DIR")
then
f1="ssh -b root#$addr<<EOF"
list = "cd /var/www/media\n"
if($# -eq "2")
then
list=list+"mkdir $2\nEOF\n"
else
num=2
i=$(($num))
while($num < $#)
do
i=$(($num))
list=list+"mkdir $i\n"
list=list+"cd $i\n"
$num=$num+1
done
fi
echo $list
elif ($1 -eq "F")
then
#list = "cd /var/www/media\n"
f2="rsync -rsh=\'ssh -p22\' "
f3 = "root#$addr:/var/www/media"
if($# -eq "2")
then
f2=f2+$2+" "+f3
else
num=3
i=$(($num))
while($num < $#)
do
i=$(($num))
f2=f2+"/"+$i
$num=$num+1
done
i=$(($num))
f2=f2+$i+" "+$f3
fi
echo $f2
fi
exit
output:
(prompt)$ ./upload2 F SO test.txt
./upload2: line 3: 3: command not found
./upload2: line 6: F: command not found
./upload2: line 25: F: command not found
So as you can see I'm having issues handling input. Its been awhile since I've done bash. And it was never extensive to begin with. Looking for a solution to my problem but also suggestions. Thanks in advance.
For comparisons, use [[ .. ]]. ( .. ) is for running commands in subshells
Don't use -eq for string comparisons, use =.
Don't use < for numerical comparisons, use -lt
To append values, f2="$f2$i $f3"
To add line feeds, use $'\n' outside of double quotes, or a literal linefeed inside of them.
You always need "$" on variables in strings to reference them, otherwise you get the literal string.
You can't use spaces around the = in assignments
You can't use $ before the variable name in assignments
To do arithmetics, use $((..)): result=$((var1+var2))
For indirect reference, such as getting $4 for n=4, use ${!n}
To prevent word splitting removing your line feeds, double quote variables such as in echo "$line"
Consider writing smaller programs and checking that they work before building out.
Here is how I would have written your script (slightly lacking in parameter checking):
#!/bin/bash
addr=$(ifconfig -a | ./test)
if [[ $1 = "DIR" ]]
then
shift
( IFS=/; echo ssh "root#$addr" mkdir -p "/var/www/media/$*"; )
elif [[ $1 = "F" ]]
then
shift
last=$#
file=${!last}
( IFS=/; echo rsync "$file" "root#$addr:/var/www/media/$*" )
else
echo "Unknown command '$1'"
fi
$* gives you all parameters separated by the first character in $IFS, and I used that to build the paths. Here's the output:
$ ./scriptname DIR a b c d
ssh root#somehost mkdir -p /var/www/media/a/b/c/d
$ ./scriptname F a b c d somefile.txt
rsync somefile.txt root#somehost:/var/www/media/a/b/c/d/somefile.txt
Remove the echos to actually execute.
The main problem with your script are the conditional statements, such as
if ($# -le "1")
Despite what this would do in other languages, in Bash this is essentially saying, execute the command line $# -le "1" in a subshell, and use its exit status as condition.
in your case, that expands to 3 -le "1", but the command 3 does not exist, which causes the error message
./upload2: line 3: 3: command not found
The closest valid syntax would be
if [ $# -le 1 ]
That is the main problem, there are other problems detailed and addressed in that other guy's post.
One last thing, when you're assigning value to a variable, e.g.
f3 = "root#$addr:/var/www/media"
don't leave space around the =. The statement above would be interpreted as "run command f3 with = and "root#$addr:/var/www/media" as arguments".

Variable with '-' (minus signals) in Bash

This is so simple yet...
FOLDER='/home/user/.ssh'
SSH="$FOLDER/local-rsync-key.pub"
if [ -f "$SSH" ]; then
...
It looks that Bash considers the '-' as a minus signal and the IF statement always fails... How can I write this variable the right way?
UPDATE:
This is another real example:
I am tring to rename files with "-" in front of the filename, for example: "-0001.jpg"
However, everyime I try to run:
for i in *; do mv "$i" "${i//-/}"; done
or:
for i in *; do mv "$i" "${i#*-}"; done
I got this error:
mv: invalid option -- '0'
Try `mv --help' for more information.
Thanks for any light!
You should not have a $ in front of your SSH assignment, that's only needed when you're using the variable. Without that, it works fine, as in the following transcript:
pax> touch abc-xyz
pax> ll a*
-rw-r--r-- 1 pax paxgrp 0 2011-06-24 05:15 abc-xyz
pax> FOLDER=.
pax> $SSH="$FOLDER/abc-xyz"
bash: =./abc-xyz: No such file or directory
pax> SSH="$FOLDER/abc-xyz"
pax> if [ -f "$SSH" ]
...> then
...> echo yes
...> fi
yes
pax> _
The answer is to use "--" (indicating no more options) after "mv" or "./" before the name of the file (indicating it is about a file). For example:
for i in *; do mv -- "$i" "${i#*-}"; done
or:
for i in *; do mv -- "$i" "./${i#*-}"; done
In bash syntax, when you set a variable just use the variable name:
VAR=value
When you reference the variable, use the $ prefix:
echo $VAR
Your code has a stray dollar sign prefix where you are trying to set the SSH variable. The dashes inside the variable should be no problem.

How to loop in bash script?

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

Resources