I can't seem to be able to combine multiple if conditions with contains function, see this example:
> function if_test
if contains "h" $argv; or contains "-h" $argv
functions if_test
return
end
end
> if_test h
# ... works fine.
> if_test -h
# ... shows documentation of contains
This is weird because the following functions works as intended, so why is this different?
> if true; and false
echo what
else
echo how
end
how
> if true; or false
echo what
else
echo how
end
what
The -h is interpreted as an option to contains - the short form of --help, so it prints contains help. Use -- to signal the end of options:
if contains h -- $argv; or contains -- -h $argv
Note that the quotes here are merely a placebo - "h" is exactly equivalent to h. If there aren't any special characters (like spaces) quotes don't change anything.
Related
I am trying to figure out how to replace matches of regex in a file with the value of an environment variable called a substring of that matched string. Each matched String also has a default along with it, separated by a colon. What I mean is if I have a file called myFile.properties that contains:
varWithDefault=${envVar1:default1}
varSetNoDefault=${envVar2}
varEmptyValue=${emptyEnvVar}
varEmptyValueWithDefault=${emptyEnvVar:3}
varNotSetWithDefault=${notSetEnvVar:I have : a colon}
# required but no match will prob be an error message
varNotSetNoDefault=${notSetEnvVar}
And I set the following environment variables:
export envVar1=value1
export envVar2=value2
export emptyEnvVar=""
then the file should update to be:
varWithDefault=value1
varSetNoDefault=value2
varEmptyValue=
varEmptyValueWithDefault=
varNotSetWithDefault=I have : a colon
# required but no match will prob be an error message
varNotSetNoDefault=${notSetEnvVar}
I have something that for the most part works... but doesn't handle when an environment is set to empty string, nor does it handle defaults properly.
# get any variables we would like to replace in the myFile.properties file
# after the colon I say "anything except for a closing bracket" so that it will allow for colons in the value
varStrings=$( grep -Po '\${([_a-zA-Z][_a-zA-Z0-9]*)(:[^}]*)*}' myFile.properties )
# loop through the variables found and replace them with the value of the corresponding environment variable
# varString is a value that looks like: "${my_variable:my_default}"
for varString in ${varStrings[#]} ; do
# ideally grab these values from the matched regex, but I can't seem to figure out how to do that
# propName would be: "my_variable"
# defaultValue would be: "my_default"
propName=$varString[0]
defaultValue=$varString[1]
# this technically gets the values, but I would also need to remove the "${}"
propName="$( cut -d ':' -f 1- <<< "$varString" )"
defaultValue="$( cut -d ':' -f 2- <<< "$varString" )"
# $varString will be a String in the format '${my_variable:my_default}' so I need to strip the default chunk from it before doing this
# but... to get the environment variable value if there was no default:
envValue=`eval echo $varString`
# if there is a matching environment variable, do the replacement; otherwise, spit out a warning
# the -z will also fail the if check if the value is an empty string, which I don't want. I only want it to go to the else clause if it was not set. Not sure how to do that.
if [ ! -z "$envValue" ]; then
echo "Replacing $varString with environment variable value '$envValue'"
sed -i "s|$varString|$envValue|g" myFile.properties
else
# set the default value
if [[ noDefaultValueGiven ]] ; then
echo "Warning: No environment variable defined for $envVarName. String not replaced."
else
echo "Warning: No environment variable '$envVarName' defined. Using default value '$defaultValue'."
sed -i "s|$varString|$defaultValue|g" myFile.properties
fi
fi
done
The two big issues I'm having with this is:
1. How to loop through each regex match and have access to both regex groups (the sections surrounded by parenthesis)
2. How to check if an environment variable exists based on a string representation of said variable (i.e. check if "${my_variable}" is set, not just if it is empty)
Does anyone know how this should be done? I'm used to SpringBoot doing this for me.
You may do it in pure BASH regex:
re='^([^=]+=)\$\{([_a-zA-Z][_a-zA-Z0-9]*)(:([^}]*))?\}'
while IFS= read -r line; do
if [[ $line =~ $re ]]; then # match regex
#declare -p BASH_REMATCH
var="${BASH_REMATCH[2]}" # var name in group #2
if [[ -n ${!var+set} ]]; then # if var is set in env
line="${BASH_REMATCH[1]}${!var}" # use var value
elif [[ -n ${BASH_REMATCH[4]} ]]; then. # if default value is set
line="${BASH_REMATCH[1]}${BASH_REMATCH[4]}" # use default string
fi
fi
echo "$line" # print each line
done < file.properties
Output:
varWithDefault=value1
varSetNoDefault=value2
varEmptyValue=
varEmptyValueWithDefault=
varNotSetWithDefault=I have : a colon
# required but no match will prob be an error message
varNotSetNoDefault=${notSetEnvVar}
Code Demo
My pattern defined to match in if-else block is :
pat="17[0-1][0-9][0-9][0-9].AUG"
nln=""
In my script, I'm taking user input which needs to be matched against the pattern, which if doesn't match, appropriate error messages are to be shown. Pretty simple, but giving me a hard time though. My code block from the script is this:
echo "How many days' AUDIT Logs need to be searched?"
read days
echo "Enter file name(s)[For multiple files, one file per line]: "
for(( c = 0 ; c < $days ; c++))
do
read elements
if [[ $elements =~ $pat ]];
then
array[$c]="$elements"
elif [[ $elements =~ $nln ]];
then
echo "No file entered.Run script again. Exiting"
exit;
else
echo "Invalid filename entered: $elements.Run script again. Exiting"
exit;
fi
done
The format I want from the user for filenames to be entered is this:
170402.AUG
So basically yymmdd.AUG (where y-year,m-month,d-day), with trailing or leading spaces is fine. Anything other than that should throw "Invalid filename entered: $elements.Run script again. Exiting" message. Also I want to check if if it is a blank line with a "Enter" hit, it should give an error saying "No file entered.Run script again. Exiting"
However my code, even if I enter something like "xxx" as filename, which should be throwing "Invalid filename entered: $elements.Run script again. Exiting", is actually checking true against a blank line, and throwing "No file entered.Run script again. Exiting"
Need some help with handling the regular expressions' check with user input, as otherwise rest of my script works just fine.
I think as discussed in the comments you are confusing with the glob match and a regEx match, what you have defined as pat is a glob match which needs to be equated with the == operator as,
pat="17[0-1][0-9][0-9][0-9].AUG"
string="170402.AUG"
[[ $string == $pat ]] && printf "Match success\n"
The equivalent ~ match would be to something as
pat="17[[:digit:]]{4}\.AUG"
[[ $string =~ $pat ]] && printf "Match success\n"
As you can see the . in the regex syntax has been escaped to deprive of its special meaning ( to match any character) but just to use as a literal dot. The POSIX character class [[:digit:]] with a character count {4} allows you to match 4 digits followed by .AUG
And for the string empty check do as suggested by the comments from Cyrus, or by Benjamin.W
[[ $elements == "" ]]
(or)
[[ -z $elements ]]
I would not bug the user with how many days (who want count 15 days or like)? Also, why only one file per line? You should help the users, not bug them like microsoft...
For the start:
show_help() { cat <<'EOF'
bla bla....
EOF
}
show_files() { echo "${#files[#]} valid files entered: ${files[#]}"; }
while read -r -p 'files? (h-help)> ' line
do
case "$line" in
q) echo "quitting..." ; exit 0 ;;
h) show_help ; continue;;
'') (( ${#files} )) && show_files; continue ;;
l) show_files ; continue ;;
p) (( ${#files} )) && break || { echo "No files enterd.. quitting" ; exit 1; } ;; # go to processing
esac
# select (grep) the valid patterns from the entered line
# and append them into the array
# using the -P (if your grep know it) you can construct very complex regexes
files+=( $(grep -oP '17\d{4}.\w{3}' <<< "$line") )
done
echo "processing files ${files[#]}"
Using such logic you can build really powerful and user-friendly app. Also, you can use -e for the read enable the readline functions (cursor keys and like)...
But :) Consider just create a simple script, which accepts arguments. Without any dialogs and such. example:
myscript -h
same as above, or some longer help text
myscript 170402.AUG 170403.AUG 170404.AUG 170405.AUG
will do whatever it should do with the files. Main benefit, you could use globbing in the filenames, like
myscript 1704*
and so on...
And if you really want the dialog, it could show it when someone runs the script without any argument, e.g.:
myscript
will run in interactive mode...
For a bash script - say I have an input of a text with a 'place holder' for a number in this format:
[V] or [VV] or [VVV] or [VVVV], etc.
Each 'V' character inside the square brackets represents a place holder for 0 (zero) padding.
Thus, if variable (integer) - NUM=17, it should replace e.g. [VVV] 'place holder' in the text as '017', e.g. [VVVVV] as '00017', etc., and single [V] as '17' - a full number, without padding.
Code example:
NUM=17
TXT="The version of the new set will be: [V] - after your changes."
# - this is the 1 line I used for the replacement
# (escaping the square brackets with \)
RESULT="${TXT//\[V*\]/$NUM}"
echo "$RESULT"
# Returns: "The version of the new set will be: 17 - after your changes."
My solution is very basic and has problems: It does replace [V], [VV], etc. with $NUM, but it also replaces anything like [V xxx].
I need to make sure that only 'V' characters are considered as the only valid placeholder, thus - [V] or [VV] or [VVV] or [VVVV], etc. and entries like [Vxxx] are ignored.
I would also appreciate a good idea on counting the 'V' characters inside the input text, so I can apply the 0 (zero) padding.
Code example:
LEN= # get the length of the [VVVVV] place holder from $TXT to var. LEN - please advise
RESULT=${TXT//[V*]/$(printf '%0'"$LEN"'d' $(( 10#$NUM )))}
I appreciate any ideas to make this work and simple.
Thank you.
Thank you for the hint RBH.
Here is a solution I came us with; do you guys see anything wrong with this?:
Code sample:
NUM=17
TXT="The version of the new set will be: [V]; Format 1: [VVV] Format 2: [VVVV] Format 3: [VVVVVVVVV]"
echo $TXT
V_LIST=$(echo "$TXT" | grep -o '\[V*\]' | sed -e "s/\[//g" -e "s/\]//g")
for V_ITEM in $V_LIST; do
TXT=${TXT//\[$V_ITEM\]/$(printf '%0'"${#V_ITEM}"'d' $(( 10#$NUM )))}
done
echo $TXT
To solve your first problem, you can use the following command:
RESULT=$(echo "$TXT" |sed -r "s/\[V+\]/$NUM/")
This sed command would replace the zero or more occurrence of V after V inside []
Let's say I have some text in a variable called $1. Now I want to check if that $1 contains a certain string. If it contains a certain string I want to print a message. The printing is not the problem, the problem is the check. Any ideas how to do that?
The easiest way in my opinion is this :
set YourString=This is a test
If NOT "%YourString%"=="%YourString:test=%" (
echo Yes
) else (
echo No
)
Basiclly the string after ':' is the string you are looking for and you are using not infront of the if because %string:*% will remove the * from the string making them not equal.
The SET search and replace trick works in many cases, but it does not support case sensitive or regular expression searches.
If you need a case sensitive search or limited regular expression support, you can use FINDSTR.
To avoid complications of escaping special characters, it is best if the search string is in a variable and both search and target are accessed via delayed expansion.
You can pipe $1 into the FINDSTR command with the ECHO command. Use ECHO( in case $1 is undefined, and be careful not to add extra spaces. ECHO !$1! will echo ECHO is off. (or on) if $1 is undefined, whereas ECHO(!$1! will echo a blank line if undefined.
FINDSTR will echo $1 if it finds the search string - you don't want that so you redirect output to nul. FINDSTR sets ERRORLEVEL to 0 if the search string is found, and 1 if it is not found. That is what is used to check if the string was found. The && and || is a convenient syntax to use to test for match (ERRORLEVEL 0) or no match (ERRORLEVEL not 0)
The regular expression support is rudimentary, but still useful.
See FINDSTR /? for more info.
This regular expression example will search $1 for "BEGIN" at start of string, "MID" anywhere in middle, and "END" at end. The search is case sensitive by default.
set "search=^BEGIN.*MID.*END$"
setlocal enableDelayedExpansion
echo(!$1!|findstr /r /c:"!search!" >nul && (
echo FOUND
rem any commands can go here
) || (
echo NOT FOUND
rem any commands can go here
)
As far as I know cmd.exe has no built-in function which answers your question directly. But it does support replace operation. So the trick is: in your $1 replace the substring you need to test the presence of with an empty string, then check if $1 has changed. If it has then it did contain the substring (otherwise the replace operation would have had nothing to replace in the first place!). See the code below:
set longString=the variable contating (or not containing) some text
#rem replace xxxxxx with the string you are looking for
set tempStr=%longString:xxxxxx=%
if "%longString%"=="%tempStr%" goto notFound
echo Substring found!
goto end
:notFound
echo Substring not found
:end
I'm trying to do some fairly simple string parsing in bash script.
Basically, I have a file that is comprised of multiple multi-line fields. Each field is surrounded by a known header and footer.
I want to extract each field separately into an array or similar, like this
>FILE=`cat file`
>REGEX="######[\s\S]+?#####"
>
>if [[$FILE =~ $REGEX ]] then
> echo $BASH_REMATCH
>fi
FILE:
######################################
this is field one
######
######################################
this is field two
they can be any number of lines
######
Now I'm pretty sure the problem is that bash doesn't match newlines with the "."
I can match this with "pcregrep -M", but of course the whole file is going to match. Can I get one match at a time from pcregrep?
I'm not opposed to using some inline perl or similar.
if you have gawk
awk 'BEGIN{ RS="##*#" }
NF{
gsub("\n"," ") #remove this is you want to retain new lines
print "-->"$0
# put to array
arr[++d]=$0
} ' file
output
$ ./shell.sh
--> this is field one
--> this is field two they can be any number of lines
The TXR language performs whole-document multi-line matching, binds variables, and (with the -B "dump bindings" option) emits properly escaped shell variable assignments that can be eval-ed. Arrays are supported.
The # character is special so it has to be doubled up to match literally.
$ cat fields.txr
#(collect)
#########################################
# (collect)
#field
# (until)
#########
# (end)
# (cat field)## <- catenate the fields together with a space separator by default
#(end)
$ txr -B fields.txr data
field[0]="this is field one"
field[1]="this is field two they can be any number of lines"
$ eval $(txr -B fields.txr data)
$ echo ${field[0]}
this is field one
$ echo ${field[1]}
this is field two they can be any number of lines
The #field syntax matches an entire line. These are collected into a list since it is inside a #(collect), and the lists are collected into lists-of-lists because that is nested inside another #(collect). The inner #(cat field) however, reduces the inner lists to a single string, so we end up with a list of strings.
This is "classic TXR": how it was originally designed and used, sparked by the idea:
Why don't we make here-documents work backwards and do parsing from reams of text into variables?
This implicit emission of matched variables by default, in the shell syntax by default, continues to be a supported behavior even though the language has grown much more powerful, so there is less of a need to integrate with shell scripts.
I would build something around awk. Here is a first proof of concept:
awk '
BEGIN{ f=0; fi="" }
/^######################################$/{ f=1 }
/^######$/{ f=0; print"Field:"fi; fi="" }
{ if(f==2)fi=fi"-"$0; if(f==1)f++ }
' file
begin="######################################"
end="######"
i=0
flag=0
while read -r line
do
case $line in
$begin)
flag=1;;
$end)
((i++))
flag=0;;
*)
if [[ $flag == 1 ]]
then
array[i]+="$line"$'\n' # retain the newline
fi;;
esac
done < datafile
If you want to keep the marker lines in the array elements, move the assignment statement (with its flag test) to the top of the while loop before the case.