Expecting Done error with BASH script - if-statement

I am trying to run some simple if/then/elif script, but I'm running into an error
echo $line2
If [[ $line2 == "shakespere/mid" ]] ; then
$line2 = "user/samples/mid"
mkdir /home/user/documents/mid
mkdir /home/user/documents/mid/complete
mv /user/samples/shakespere/mid/fulltext.txt /home/user/documents/mid/complete
elif [[ $line2 == "shakespere/rnj" ]] ; then
$line2 = "user/samples/rnj"
mkdir /home/user/documents/rnj
mkdir /home/user/documents/rnj/complete
mv /user/samples/shakespere/rnj/fulltext.txt /home/user/documents/rnj/complete
elif [[ $line2 == "shakespere/lll" ]] ; then
$line2 = "user/samples/lll"
mkdir /home/user/documents/lll
mkdir /home/user/documents/lll/complete
mv /user/samples/shakespere/lll/fulltext.txt /home/user/documents/lll/complete
elif [[ $line2 == "shakespere/misc ]] " ; then
$line2 = "user/samples/misc"
mkdir /home/user/documents/misc
mkdir /home/user/documents/misc/complete
mv /user/samples/shakespere/misc/fulltext.txt /home/user/documents/misc/complete
else
$line2 = "user/samples/son"
mkdir /home/user/documents/son
mkdir /home/user/documents/son/complete
mv /user/samples/shakespere/son/fulltext.txt /home/user/documents/son/complete
fi
I get this error: "Syntax error: "then" unexpected (expecting "done")"
I'm sure it's something simple, but it's killing me not knowing why.
I've even mad them all if/then statements - nope. no good.
Running on Raspbian - latest update on RPi3b

real simple.. UPPER case I in IF doesn't work.
it must be "if"
sorry, my grammar nazi came out during the writing of the script.

Related

Bash: wildcard in argument expands to a filename [duplicate]

This question already has answers here:
When to wrap quotes around a shell variable?
(5 answers)
I just assigned a variable, but echo $variable shows something else
(7 answers)
Closed 4 years ago.
This is my script:
#!/bin/bash
set -x
arguments="some explanation"
if [[ $1 == -f ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d %H,%M,%S.%%e" "$2"
elif [[ $1 == -d ]]; then
exiftool -d %Y-%m "-directory<datetimeoriginal" "$2"
elif [[ $1 == -fd ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d %H,%M,%S.%%e" "$2"
exiftool -d %Y-%m "-directory<datetimeoriginal" "$2"
elif [[ $1 == -p ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d.%%e" "$2"
else
echo -e $arguments
fi
set +x
If I run exifrename -f . it renames all files in the current folder. But let's say I have the following 3 files:
IMAG01234.jpg
IMAG01235.jpg
Ralph at home.jpg
and with them I run `exifrename -f IMAG*. It only renames one IMAG file.
When I debug this script, I see this:
+ [[ -d == -f ]]
+ [[ -d == -d ]]
+ exiftool -d %Y-%m '-directory<datetimeoriginal' IMAG01235.jpg
What can I do make the last line say IMAG* and not IMAG01235?
When you are doing IMAG*, then the bash shell does globbing and expands IMAG* to 2 files and passes them as arguments to your script. You need to iterate through all the command line arguments:
Example:
#!/bin/bash
option="$1" # this contains -f, -d, or something.
shift 1
for filename in "$#"; do
ls -al "$filename" # Just an example. But you get the point.
done
In your case, you could do the following assuming, that there would be always be one option and followed by list of files.
option="$1"
shift 1
for filename in "$#"; do
if [[ "$option" == '-f' ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d %H,%M,%S.%%e" "$filename"
elif [[ "$option" == '-d' ]]; then
exiftool -d %Y-%m "-directory<datetimeoriginal" "$filename"
elif [[ "$option" == '-fd' ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d %H,%M,%S.%%e" "$filename"
exiftool -d %Y-%m "-directory<datetimeoriginal" "$filename"
elif [[ "$option" == '-p' ]]; then
exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d.%%e" "$filename"
else
echo -e $arguments
fi
done
Note: If you comfortable with bash scripting, it is more readable to do with case/esac instead of bunch of if/else.

Running multi-line bash script as string from C++ code

I want to run following bash script from C++ code. I tries to use system() or popen to run commands and capture its output but they but I get errors because built-in sh tries to execute it, such as,
sh: 6: [[: not found
sh: 8: [[: not found
sh: 9: [[: not found
I tried bash -c as well but that also produced errors because I think it doesn't handle multiline string.
I can't put below script in to .sh file and run it because of several reasons. So this script needs to be stored as a string in C++ code and get executed. Any idea how this can be done?
#!/bin/bash
for sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do
(
syspath="${sysdevpath%/dev}"
devname="$(udevadm info -q name -p $syspath)"
[[ "$devname" == "bus/"* ]] && continue
eval "$(udevadm info -q property --export -p $syspath)"
[[ -z "$ID_SERIAL" ]] && continue
[[ "${ID_SERIAL}" == *"PX4"* ]] && echo "/dev/$devname"
)
done
Sample code:
Note: You can use this tool to convert text to C++ escapped string.
int main() {
std::cout << system("#!/bin/bash\nfor sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do\n (\n syspath=\"${sysdevpath%/dev}\"\n devname=\"$(udevadm info -q name -p $syspath)\"\n [[ \"$devname\" == \"bus/\"* ]] && continue\n eval \"$(udevadm info -q property --export -p $syspath)\"\n [[ -z \"$ID_SERIAL\" ]] && continue\n [[ \"${ID_SERIAL}\" == *\"PX4\"* ]] && echo \"/dev/$devname\"\n )\ndone");
return 0;
}
You can turn a multiline bash script to single-line. Let's assume you have the following bash script:
FOO=`uname`
if [ "$FOO" == "Linux" ]; then
echo "You are using 'Linux'"
fi
The code above can be transformed into single-line by using semicolons:
FOO=`uname`; if [ "$FOO" == "Linux" ]; then echo "You are using 'Linux'"; fi
Now with proper escaping you can use system command to execute it from your c++ program as follows:
#include <cstdlib>
#include <string>
int main() {
std::string foo {
"bash -c '"
"FOO=`uname`; "
"if [ \"$FOO\" == \"Linux\" ]; then "
"echo \"You are using 'Linux'.\"; "
"fi'"
};
system(foo.c_str());
}
Note that adjacent string literals are concatenated by the compiler, so you can still make it look like a multiline script for better readability.

Renaming directories based on a pattern in Bash

I have a Bash script that works well for just renaming directories that match a criteria.
for name in *\[*\]\ -\ *; do
if [[ -d "$name" ]] && [[ ! -e "${name#* - }" ]]; then
mv "$name" "${name#* - }"
fi
done
Currently if the directory looks like:
user1 [files.sentfrom.com] - Directory-Subject
It renames the directory and only the directory to look like
Directory-Subject (this could have different type of text)
How can I change the script / search criteria to now search for
www.ibm.com - Directory-Subject
and rename the directory and only the directory to
Directory-Subject
You could write your code this way so that it covers both the cases:
for dir in *\ -\ *; do
[[ -d "$dir" ]] || continue # skip if not a directory
sub="${dir#* - }"
if [[ ! -e "$sub" ]]; then
mv "$dir" "$sub"
fi
done
Before running the script:
$ ls -1d */
user1 [files.sentfrom.com] - Directory-Subject/
www.ibm.com - Directory-Subject
After:
$ ls -1d */
Directory-Subject/
www.ibm.com - Directory-Subject/ # didn't move because directory existed already
A simple answer would be to change *\[*\]\ -\ * to *\ -\ *
for name in *\ -\ *; do
if [[ -d "$name" ]] && [[ ! -e "${name#* - }" ]]; then
mv "$name" "${name#* - }"
fi
done
For more information, please read glob and wildcards

Splitting all txt files in a folder into smaller files based on a regular expression using bash

I have a folder containing large text files. Each file is a collection of 1000 files separated by [[ file name ]]. I want to split the files and make 1000 files out of them and put them in a new folder. Is there a way in bash to do it? Any other fast method will also do.
for f in $(find . -name '*.txt')
do mkdir $f
mv
cd $f
awk '/[[.*]]/{g++} { print $0 > g".txt"}' $f
cd ..
done
You are trying to create a folder with the same name of the already existing file.
for f in $(find . -name '*.txt')
do mkdir $f
Here, "find" will list the files in the current path, and for each of these files you will try to create a directory with exactly the same name. One way of doing it would be first creating a temporary folder:
for f in $(find . -name '*.txt')
do mkdir temporary # create a temporary folder
mv $f temporary # move the file into the folder
mv temporary $f # rename the temporary folder to the name of the file
cd $f # enter the folder and go on....
awk '/[[.*]]/{g++} { print $0 > g".txt"}' $f
cd ..
done
Note that all your folders will have the ".txt" extension. If you don't want that, you can cut it out before creating the folder; that way, you won't need the temporary folder, because the folder you're trying to create has a different name from the .txt file.
Example:
for f in $(find . -name '*.txt' | rev | cut -b 5- | rev)
Although not awk and written and written by a drunk person, not guaranteed to work.
import re
import sys
def main():
pattern = re.compile(r'\[\[(.+)]]')
with open (sys.argv[1]) as f:
for line in f:
m = re.search(pattern, line)
if m:
try:
with open(fname, 'w+') as g:
g.writelines(lines)
except NameError:
pass
fname = m.group(1)
lines = []
else:
lines.append(line)
with open(fname, 'w+') as g:
g.writelines(lines)
if __name__ == '__main__':
main()
Write a bash script. Here, I've done it for you.
Notice the structure and features of this script:
explain what it does in a usage() function, which is used for the -h option.
provide a set of standard options: -h, -n, -v.
use getopts to do option processing
do lots of error checking on the arguments
be careful about filename parsing (notice that blanks surrounding the file names are ignored.
hide details within functions. Notice the 'talk', 'qtalk', 'nvtalk' functions? Those are from a bash library I've built to make this kind of scripting easy to do.
explain what is going on to the user if in $verbose mode.
provide the user the ability to see what would be done without actually doing it (the -n option, for $norun mode).
never run commands directly. but use the run function, which pays attention to the $norun, $verbose, and $quiet variables.
I'm not just fishing for you, but teaching you how to fish.
Good luck with your next bash script.
Alan S.
#!/bin/bash
# split-collections IN-FOLDER OUT-FOLDER
PROG="${0##*/}"
usage() {
cat 1>&2 <<EOF
usage: $PROG [OPTIONS] IN-FOLDER OUT-FOLDER
This script splits a collection of files within IN-FOLDER into
separate, named files into the given OUT-FOLDER. The created file
names are obtained from formatted text headers within the input
files.
The format of each input file is a set of HEADER and BODY pairs,
where each HEADER is a text line formatted as:
[[input-filename1]]
text line 1
text line 2
...
[[input-filename2]]
text line 1
text line 2
...
Normal processing will show the filenames being read, and file
names being created. Use the -v (verbose) option to show the
number of text lines being written to each created file. Use
-v twice to show the actual lines of text being written.
Use the -n option to show what would be done, without actually
doing it.
Options
-h Show this help
-n Dry run -- do NOT create any files or make any changes
-o Overwrite existing output files.
-v Be verbose
EOF
exit
}
talk() { echo 1>&2 "$#" ; }
chat() { [[ -n "$norun$verbose" ]] && talk "$#" ; }
nvtalk() { [[ -n "$verbose" ]] || talk "$#" ; }
qtalk() { [[ -n "$quiet" ]] || talk "$#" ; }
nrtalk() { talk "${norun:+(norun) }$#" ; }
error() {
local code=2
case "$1" in [0-9]*) code=$1 ; shift ;; esac
echo 1>&2 "$#"
exit $code
}
talkf() { printf 1>&2 "$#" ; }
chatf() { [[ -n "$norun$verbose" ]] && talkf "$#" ; }
nvtalkf() { [[ -n "$verbose" ]] || talkf "$#" ; }
qtalkf() { [[ -n "$quiet" ]] || talkf "$#" ; }
nrtalkf() { talkf "${norun:+(norun) }$#" ; }
errorf() {
local code=2
case "$1" in [0-9]*) code=$1 ; shift ;; esac
printf 1>&2 "$#"
exit $code
}
# run COMMAND ARGS ...
qrun() {
( quiet=1 run "$#" )
}
run() {
if [[ -n "$norun" ]]; then
if [[ -z "$quiet" ]]; then
nrtalk "$#"
fi
else
if [[ -n "$verbose" ]]; then
talk ">> $#"
fi
if ! eval "$#" ; then
local code=$?
return $code
fi
fi
return 0
}
show_line() {
talkf "%s:%d: %s\n" "$in_file" "$lines_in" "$line"
}
# given an input filename, read it and create
# the output files as indicated by the contents
# of the text in the file
split_collection() {
in_file="$1"
out_file=
lines_in=0
lines_out=0
skipping=
while read line ; do
: $(( lines_in++ ))
[[ $verbose_count > 1 ]] && show_line
# if a line with the format of "[[foo]]" occurs,
# close the current output file, and open a new
# output file called "foo"
if [[ "$line" =~ ^\[\[[[:blank:]]*([^ ]+.*[^ ]|[^ ])[[:blank:]]*\]\][[:blank:]]*$ ]] ; then
new_file="${BASH_REMATCH[1]}"
# close out the current file, if any
if [[ "$out_file" ]]; then
nrtalkf "%d lines written to %s\n" $lines_out "$out_file"
fi
# check the filename for bogosities
case "$new_file" in
*..*|*/*)
[[ $verbose_count < 2 ]] && show_line
error "Badly formatted filename"
;;
esac
out_file="$out_folder/$new_file"
if [[ -e "$out_file" ]]; then
if [[ -n "$overwrite" ]]; then
nrtalk "Overwriting existing '$out_file'"
qrun "cat /dev/null >'$out_file'"
else
error "$out_file already exists."
fi
else
nrtalk "Creating new output file: '$out_file' ..."
qrun "touch '$out_file'"
fi
lines_out=0
elif [[ -z "$out_file" ]]; then
# apparently, there are text lines before the filename
# header; ignore them (out loud)
if [[ ! "$skipping" ]]; then
talk "Text preceding first filename ignored.."
skipping=1
fi
else # next line of input for the file
qrun "echo \"$line\" >>'$out_file'"
: $(( lines_out++ ))
fi
done
}
norun=
verbose=
verbose_count=0
overwrite=
quiet=
while getopts 'hnoqv' opt ; do
case "$opt" in
h) usage ;;
n) norun=1 ;;
o) overwrite=1 ;;
q) quiet=1 ;;
v) verbose=1 ; : $(( verbose_count++ )) ;;
esac
done
shift $(( OPTIND - 1 ))
in_folder="${1:?Missing IN-FOLDER; see $PROG -h for details}"
out_folder="${2:?Missing OUT-FOLDER; see $PROG -h for details}"
# validate the input and output folders
#
# It might be reasonable to create the output folder for the
# user, but that's left as an exercise for the user.
in_folder="${in_folder%/}" # remove trailing slash, if any
out_folder="${out_folder%/}"
[[ -e "$in_folder" ]] || error "$in_folder does not exist"
[[ -d "$in_folder" ]] || error "$in_folder is not a directory."
[[ -e "$out_folder" ]] || error "$out_folder does not exist."
[[ -d "$out_folder" ]] || error "$out_folder is not a directory."
for collection in $in_folder/* ; do
talk "Reading $collection .."
split_collection "$collection" <$collection
done
exit

Regular expression alteration not matching in BASH

I have been trying to get multiple not matching alteration to work in a BASH.
This is what I am trying to not match. If there are two parameters and a switch(-a,-b,-c,-d) is the first parameter.
Example:
./scriptname -a filename
./scriptname -d filename
What I want this to echo success is for:
./scriptname filename ipaddress.
The code that works is :
if [[ "$#" = "2" && "$1" =~ ([^-a][^-b][^-c]) ]]
then
echo "success"
else
echo "fail"
fi
If I try to expand on the alteration with ([^-a][^-b][^-c][^-d]) it stops working. I have tried multiple syntax variants and nothing seems to work. I also tried to group them together like:
if [[ "$#" = "2" && "$1" =~ ([^-a][^-b]) && "$1" =~ ([^-c][^-d]) ]] and this fails as well.
What about:
if [[ "$#" = "2" && "$1" =~ -[a-d]$ ]]