Get output of command; run another command if contains X - if-statement

I want to write a bash script that:
Runs the command bux
a) If the output of bux contains have, do nothing
b) If the output of bux contains X, run the command Y
c) If the output of bux contains Z, run the command A
It will only contain one of these things, not multiple x

Here is a script that should do what you want (provided that bux, Y and A are bash scripts) :
#!/bin/bash
OUTPUT=`source bux`
if [[ "$OUTPUT" =~ have ]]; then
:
elif [[ "$OUTPUT" =~ X ]]; then
source Y
elif [[ "$OUTPUT" =~ Z ]]; then
source A
fi
If you want to execute programs instead (provided that bux, Y and A are in the path) :
#!/bin/bash
OUTPUT=`bux`
if [[ "$OUTPUT" =~ have ]]; then
:
elif [[ "$OUTPUT" =~ X ]]; then
Y
elif [[ "$OUTPUT" =~ Z ]]; then
A
fi

glob patterns in a case statement like this:
case $(bux) in
*have*)
echo 'do nothing?'
;;
*X*)
Y
;;
*Z*)
A
;;
*)
echo 'default case.' # display an error ...?
;;
esac
Obviously the patterns could be more complex if you like, but this seems to cover your requirements.

Related

Parsing string with two captures in bash

I'm trying to parse a string with regex. A valid string is of the following format:
https://github.com/xyz/abc/a_123/project_14.git
The valid string should contain github.com and xyz or zyx. If the string is valid I want to capture abc/a_123 into $A and project_14 into $B.
What I did:
if [[ "$x" == *"github.com"* ]]; then
if [[ "$x" == *"xyz"* ]]; then
# (1)
elif [[ "$x" == *"zyx"* ]]; then
# (2)
else
return 1 # Invalid
fi
return 0 # Valid
fi
return 1 # Invalid
In both (1) and (2) I want to set $A and $B with the values (same behavior on different cases).
Also, I think that this solution is not good because it will enter the if-else in the case of https://github.com/bla/abc/a_123/xyz.git so I guess we need to change it to be "github.com/xyz". Also, how can I get rid of .git (if exists)?
Another example:
https://github.com/zyx/asdasdas/lalal/asdas/nu.git
# $A = asdasdas/lalal/asdas
# $B = nu
What is the proper way to achieve this goal?
Here is a way using regex:
url='https://github.com/xyz/abc/a_123/project_14.git'
if [[ $url =~ http[s]?:[/]{2}(github.com)[/]([[:alpha:]]+)(/.*)$ ]]
then
$A=${BASH_REMATCH[2]}
$B=${BASH_REMATCH[3]%.git}
fi
And here is a small proof of concept:
url='https://github.com/xyz/abc/a_123/project_14.git'
if [[ $url =~ http[s]?:[/]{2}(github.com)[/]([[:alpha:]]+)(/.*)$ ]]
then
echo ${BASH_REMATCH[2]} ${BASH_REMATCH[3]%.git}
fi
Resulting in:
xyz /abc/a_123/project_14
I think this does what you want :
#!/bin/bash
repo="https://github.com/xyz/abc/a_123/project_14.git"
[[ ! "$repo" =~ https:\/\/github.com\/[a-z]+\/[a-z]+\/[a-z]_[0-9]+\/.*.git ]] && exit
A=$( echo "$repo" | sed -E "s/(https:\/\/github.com\/[a-z]+)(\/[a-z]+\/[a-z]_[0-9]+\/)(.*.git)/\2/g" )
B=$( echo "$repo" | sed -E "s/(https:\/\/github.com\/[a-z]+)(\/[a-z]+\/[a-z]_[0-9]+\/)(.*.git)/\3/g" )
echo "$A"
echo "${B%%.git}"
Let me know if it helps
Would you please try the following:
strchk() {
local x=$1
if [[ $x =~ github.com/(xyz|zyx)/(.+)/(.+) ]]; then
A="${BASH_REMATCH[2]}"
B="${BASH_REMATCH[3]%.*}"
return 0
else
return 1
fi
}
Results:
strchk "https://github.com/xyz/abc/a_123/project_14.git" && echo "A=$A, B=$B"
=> A=abc/a_123, B=project_14
strchk "https://github.com/bla/abc/a_123/xyz.git" && echo "A=$A, B=$B"
=> <empty>
strchk "https://github.com/zyx/asdasdas/lalal/asdas/nu.git" && echo "A=$A, B=$B"
=> A=asdasdas/lalal/asdas, B=nu
Explanations:
The pattern github.com/(xyz|zyx)/ matches a string which contains
github.com/ followed by xyz/ or zyx/.
The next pattern (.+)/ matches a substring after xyz/ or zyx/ as long
as it reaches the rightmost slash then stores the captured substring within the parens into
a bash variable ${BASH_REMATCH[2]}.
The last pattern (.+) captures the remaining substring into
${BASH_REMATCH[3]}.
The parameter expansion ${BASH_REMATCH[3]%.*} removes the extension
after the dot if exists.
Hope this helps.

Weird regex behavior in bash if condition

I have written a small script that loops through directories (starting from a given argument directory) and prompts directories that have an xml file inside. Here is my code :
#! /bin/bash
process()
{
LIST_ENTRIES=$(find $1 -mindepth 1 -maxdepth 1)
regex="\.xml"
if [[ $LIST_ENTRIES =~ $regex ]]; then
echo "$1"
fi
# Process found entries
while read -r line
do
if [[ -d $line ]]; then
process $line
fi
done <<< "$LIST_ENTRIES"
}
process $1
This code works fine. However, if I change the regex to \.xml$ to indicate that it should match at the end of the line, the result is different, and I do not get all the right directories.
Is there something wrong with this ?
Your variable LIST_ENTRIES may not have .xml as the last entry.
To validate, try echo $LIST_ENTRIES.
To overcome this, use for around your if:
process()
{
LIST_ENTRIES=$(find $1 -mindepth 1 -maxdepth 1)
regex="\.xml$"
for each in $LIST_ENTRIES; do
if [[ $each =~ $regex ]]; then
echo "$1"
fi
done
# Process found entries
while read -r line
do
if [[ -d $line ]]; then
process $line
fi
done <<< "$LIST_ENTRIES"
}
process $1

How to match this string in bash?

I'm reading a file in bash, line by line. I need to print lines that have the following format:
don't care <<< at least one character >>> don't care.
These are all the way which I have tried and none of them work:
if [[ $line =~ .*<<<.+>>>.* ]]; then
echo "$line"
fi
This has incorrect syntax
These two have correct syntax don't work
if [[ $line =~ '.*<<<.+>>>.*' ]]; then
echo "$line"
fi
And this:
if [[ $line == '*<<<*>>>*' ]]; then
echo "$line"
fi
So how to I tell bash to only print lines with that format? PD: I have tested and printing all lines works just fine.
Don't need regular expression. filename patterns will work just fine:
if [[ $line == *"<<<"?*">>>"* ]]; then ...
* - match zero or more characters
? - match exactly one character
"<<<" and ">>>" - literal strings: The angle brackets need to be quoted so bash does not interpret them as a here-string redirection.
$ line=foobar
$ [[ $line == *"<<<"?*">>>"* ]] && echo y || echo n
n
$ line='foo<<<>>>bar'
$ [[ $line == *"<<<"?*">>>"* ]] && echo y || echo n
n
$ line='foo<<<x>>>bar'
$ [[ $line == *"<<<"?*">>>"* ]] && echo y || echo n
y
$ line='foo<<<xyz>>>bar'
$ [[ $line == *"<<<"?*">>>"* ]] && echo y || echo n
y
For maximum compatibility, it's always a good idea to define your regex pattern as a separate variable in single quotes, then use it unquoted. This works for me:
re='<<<.+>>>'
if [[ $line =~ $re ]]; then
echo "$line"
fi
I got rid of the redundant leading/trailing .*, by the way.
Of course, I'm assuming that you have a valid reason to process the file in native bash (if not, just use grep -E '<<<.+>>>' file)
<, <<, <<<, >, and >> are special in the shell and need quoting:
[[ $line =~ '<<<'.+'>>>' ]]
. and + shouldn't be quoted, though, to keep their special meaning.
You don't need the leading and trailing .* in =~ matching, but you need them (or their equivalents) in patterns:
[[ $line == *'<<<'?*'>>>'* ]]
It's faster to use grep to extract lines:
grep -E '<<<.+>>>' input-file
I don't even understand why you are reading the file line per line. I have just launched following command in the bash prompt and it's working fine:
grep "<<<<.+>>>>" test.txt
where test.txt contains following data:
<<<<>>>>
<<<<a>>>>
<<<<aa>>>>
The result of the command was:
<<<<a>>>>
<<<<aa>>>>

Shell script to validate hex value

How to validate whether the given input string is valid Hex value or not using regex in shell scripts
For example:
Input var="ff:ff:fe:ff"
There is a : deliminator value
I want to use this regex for any input String
var = "ff:ff:fe:ff:fe"
var = "ff:ff:fe:ff:fe:fe:ff:ff"
\b0[xX][0-9a-fA-F]+\b
#!/bin/bash -x
var="fe:fe:fe:fe"
regex="/^([0-9A-F]+:?){4}$/"
if [[ $var =~ $regex ]]; then
echo "valid"
fi
Better version (thanks to chepner):
^([[:xdigit:]]{2})(:[[:xdigit:]]{2})*$
Test
if [[ "ff:af:ff:23:a2:ad" =~ ^([[:xdigit:]]{2})(:[[:xdigit:]]{2})*$ ]]; then
echo "match";
fi
Old Answer:
^([0-9A-Fa-f]{2})(:[0-9A-Fa-f]{2})*$
Test
$ if [[ "ff:af:ff:23:a2:ad" =~ ^([0-9A-Fa-f]{2})(:[0-9A-Fa-f]{2})*$ ]]; then
echo "match";
fi
$ match
$ if [[ "definitlynottherightformat" =~ ^([0-9A-Fa-f]{2})(:[0-9A-Fa-f]{2})*$ ]]; then
echo "match";
fi
$

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