How to remove certain items from an array - bash

I am trying to get a list of all Apps plus their versions using mdfind:
function get_mac_apps_info {
local list_apps=()
local app_version
local plist_info_app_path
local plist_field="CFBundleName"
readarray -d '' list_apps < <(mdfind -0 "kMDItemContentType == com.apple.application-bundle")
for index in "${!list_apps[#]}"
do [[ ${list_apps[$index]} =~ '^(?!.*\(Parallels\)).*' ]] && unset -v 'list_apps[$index]'
done
for app_path in "${list_apps[#]}"; do
plist_info_app_path="$app_path/Contents/Info.plist"
if [[ -f "$plist_info_app_path" ]]; then
app_version="$(get_version_from_plist "$app_path" 2>/dev/null)"
app_name="$(get_field_from_plist "$app_path" "$plist_field" 2>/dev/null)"
if [[ $app_version ]]; then
echo "$app_version;$app_name"
fi
fi
done
}
Thing is Parallels Desktop is installed and gets a lot of entries like these when populating the mdfind array:
/Users/user-test/Applications (Parallels)/{8dcf6541-4642-4aa0-b6ef-f73b59c0005e} Applications.localized/Command Prompt.app
/Users/user-test/Applications (Parallels)/{9bfd84de-a9b0-445d-afd5-c95690c3d1ea} Applications.localized/Command Prompt.app
I am trying to filter this out (unsuccessfully):
for index in "${!list_apps[#]}"
do [[ ${list_apps[$index]} =~ '^(?!.*\(Parallels\)).*' ]] && unset -v 'list_apps[$index]'
done
Any suggestions?
Thanks!

Try:
for index in "${!list_apps[#]}"; do
[[ ${list_apps[index]} == *'(Parallels)'* ]] && unset 'list_apps[index]'
done
Because of the single quotes, =~ '^(?!.*\(Parallels\)).*' only matches strings that contain the literal string '^(?!.*\(Parallels\)).*' (no special meaning is given to ^, (, ., etc.). If you remove the quotes it still doesn't work because it uses regular expression features that aren't supported by Bash.
The code above uses Bash glob pattern matching to match strings that contain the literal string (Parallels).

Related

how to check whether a string starts with xx and ends with yy in shellscript?

In the below example I want to find whether the sentence starts with 'ap' and ends with 'e'.
example: a="apple"
if [[ "$a" == ^"ap"+$ ]]
This is not giving proper output.
You don't mention which shell you're using, but the [[ in your attempt suggests you're using one that expands upon the base POSIX sh language. The following works with at least bash, zsh and ksh93:
$ a=apple
$ [[ $a == ap*e ]] && echo matches # Wildcard pattern
matches
$ [[ $a =~ ^ap.*e$ ]] && echo matches # Regular expression - note the =~
matches

How to strip or replace all matches of a pattern from a string in Bash?

I am trying to write the dirname function in Bash so that it doesn't use any external commands.
function dirname() {
local path=$1
[[ $path =~ ^[^/]+$ ]] && dir=. || { # if path has no slashes, set dir to .
[[ $path =~ ^/+$ ]] && dir=/ || { # if path has only slashes, set dir to /
local IFS=/ dir_a i
read -ra dir_a <<< "$path" # read the components of path into an array
dir="${dir_a[0]}"
for ((i=1; i < ${#dir_a[#]}; i++)); do # strip out any repeating slashes
[[ ${dir_a[i]} ]] && dir="$dir/${dir_a[i]}" # append unless it is an empty element
done
}
}
[[ $dir ]] && printf '%s\n' "$dir" # print only if not empty
}
In order to remove any repeating / from the path, I had to use the array logic. Is there a simpler way to do the same with Bash Parameter Expansion? I tried, but I don't seem to get it right.
Basically, I want to replace all occurrences of multiple consecutive slashes with a single slash each.
If extglob is on:
shopt -s extglob
you can do this:
printf '%s\n' "${path//\/+(\/)/\/}"
This uses the ${var//pattern/replacement} syntax to do a global substitution.
The pattern is \/+(\/) (with escaped slashes because / is the delimiter), which is really /+(/) (where +(/) means "one or more slashes").

Detecting when a string exists but doesn't start with - in bash

I am trying to make a bash program that saves results to a file with the name of the user's choosing if the program is supplied the --file argument followed by an option, in which the option should not start with a dash. So I used the following conditional:
if [[ -n $2 && !($2="[^-]") ]]
But that didn't work. It still saves the output to a file even if the second argument starts with a dash. I also tried using this:
1) if ! [[ -z $2 && ($2="[^-]") ]]
It also did as the previous one. What's the problem? Thanks in advance!
As a pattern match, this might look like:
[[ $2 ]] && [[ $2 != -* ]]
Note:
Moving && outside of [[ ]] isn't mandatory, but it is good form: It ensures that your code can be rewritten to work with the POSIX test command without either using obsolescent functionality (-a and -o) or needing to restructure.
Whitespace is mandatory. In !($2="[^-]"), neither the ! nor the ( and ) nor the = are parsed as separate operators.
= and != check for pattern matches, not regular expressions. The regular expression operator in [[ ]] is =~. Among the differences, anchors (^ to match at the beginning of a string, or $ to match at the end) are implicit in a pattern whereas they need to be explicit in a regex, and * has a very different meaning (* in a pattern means the same thing as .* in a regex).
The ^ in [^-] already negates the -, so by using ! in addition, you're making your code only match when there is a dash in the second argument.
To test this yourself:
$ check_args() { [[ $2 ]] && [[ $2 != -* ]]; echo $?; }
$ check_args one --two
1
$ check_args one two
0
$ check_args one
1

Logical OR in my shell script

My script:
#!/bin/bash
for file in *.ats;
do
if [[ ("${file}" = THx) || ("${file}" = THy)]]
then cp $file /home/milenko/procmt
fi
done
Files in directory
262_V01_C00_R000_TEx_BL_128H.ats
262_V01_C01_R000_TEy_BL_128H.ats
262_V01_C02_R000_THx_BL_128H.ats
262_V01_C03_R000_THy_BL_128H.ats
What I wanted is to copy the files that contain THx or THy,but files are not copied.
Why?
I think you can avoid a loop entirely here:
cp *TH[xy]*.ats /home/milenko/procmt
There's no need to loop through the results and then do a separate comparison; a single glob will expand to the list of files that you want.
There were a couple of problems with your original approach:
Firstly, you were trying to test for exact matches, so the condition would never be true.
Also, take care with spaces: ]] is a keyword in the compound command [[, so it needs to be a separate word (i.e. surrounded by spaces).
What about using extglob for extended globbing? This way you can use the for itself to get the required extensions:
shopt -s extglob
for file in *TH?(x|y)*.ats; do
# do things with "$file" ...
done
*TH?(x|y)*.ats expands to those files containing <something> + TH + either x or y + <something> + .ats
Your script fails because you have a typo in it:
if [[ ("${file}" = THx) || ("${file}" = THy)]]
# ^
# missing space
This is fine:
$ d="hi"
$ [[ ($d == hi) || ($d == ha) ]] && echo "yes"
yes
Although the parentheses are superfluous:
$ [[ $d == hi || $d == ha ]] && echo "yes"
yes
Your question specifies "files that contain THx or THy" ... but your code specifies that the file name is THx or THy.
You can use character class in glob i.e. *TH[xy]* to check if $file contains THx or THy:
for file in *.ats; do
if [[ $file == *TH[xy]* ]]; then
cp "$file" /home/milenko/procmt
fi
done

How to check if dictionary contains a key in bash?

I would like to check if a dictionary contains a key, but i dont know how.
I tried this:
if [ -z "${codeDict["$STR_ARRAY[2]"]+xxx}" ]
then
echo "codeDict not contains ${STR_ARRAY[2]}"
codeDict["${STR_ARRAY[2]}"]="${STR_ARRAY[3]}"
fi
There's nothing wrong with your approach (using -z), as this example shows:
$ declare -A a
$ a=( [a]=1 [b]=2 [d]=4 )
$ [[ -z ${a[a]} ]] && echo unset
$ [[ -z ${a[c]} ]] && echo unset
unset
However, there are a couple of issues with the code in your question. You're missing the curly braces around your inner array and personally I'd suggest that you use extended tests ([[ instead of [) to avoid having to mess around with quotes:
$ str=( a b c )
$ [[ -z ${a[${str[0]}]} ]] && echo unset
$ [[ -z ${a[${str[2]}]} ]] && echo unset
unset
If you are using bash 4.3, you can use the -v test:
if [[ -v codeDict["${STR_ARRAY[2]}"] ]]; then
# codeDict has ${STR_ARRAY[2]} as a key
else
# codeDict does not have ${STR_ARRAY[2]} as a key
fi
Otherwise, you need to take care to distinguish between keys that map to an empty string and keys that are not in the array at all.
key=${STR_ARRARY[2]}
tmp=codeDict["$key"] # Save a lot of typing
# ${!tmp} expands to the actual value (which may be the empty string),
# or the empty string if the key does not exist
# ${!tmp-foo} expands to the actual value (which may be the empty string),
# or "foo" if the key does not exist
# ${!tmp:-foo} expands to the actual value if it is a non-empty string,
# or "foo" if the key does not exist *or* the key maps
# to the empty string.
if [[ ${!tmp} = ${!tmp-foo} || ${!tmp} = ${!tmp:-foo} ]]; then
# $key is a key
else
# $key is not a key
fi
In any version of bash that supports associative arrays, you can use a simple one liner if all you want to do is provide a default value for a key that does not exist.
: ${codeDict["${STR_ARRAY[2]}"]="${STR_ARRAY[3]}"}

Resources