One of the easiest ways to check if an item is in a list is to convert that list into an associative array AKA "hash" (with the items being the keys, and any arbitrary value) and then test whether the item you want is an index of the array.
I typically use "0" or "1" as the value for each key. Sometimes I just test for an empty vs non-empty string. It mostly depends on what language I'm using and what it considers to be true or false.
Effectively, this is using an associative array as a simple set and testing for set membership (if a key has a value it's a member, if it doesn't, it isn't), so the value doesn't matter as long as you know what to test for and how to test for it.
No need for a regex match, just a simple test: Is the item I'm looking for a key in the associative array?
Testing for set membership is also fast...performance doesn't matter much for a one-off test, but it matters a lot if you're testing a large number of potential set members. This is especially true in an excruciatingly slow language like shell.
Here's an example using a list contained in indexed array.
$ items=(item1 item2 item3 item4)
$ declare -A itemhash
$ for i in "${items[@]}" ; do itemhash[$i]=1 ; done
This is what the indexed array and associative array currently contain:
$ declare -p items itemhash
declare -a items=([0]="item1" [1]="item2" [2]="item3" [3]="item4")
declare -A itemhash=([item1]="1" [item2]="1" [item3]="1" [item4]="1" )
OK, the hash (associative array) is populated, now we can test if it contains a particular item:
$ if [ "${itemhash[item1]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array
$ if [ "${itemhash[item5]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array
This method works with pretty much any list, no matter the origin of the items (an indexed array, a list of filenames, the output of a database query, whatever), and it doesn't really matter how you populate the hash - the important thing is that the keys to the hash should be the names of your items, and the values for each key should be something you can easily test for.
You shouldn't have used an example that involved filenames and ls
because that has been a huge distraction....but here's another example using testing for the filename 'multi.sh' in a list of filenames in the current directory.
First, when multi.sh
doesn't exist in current directory:
$ declare -A foo
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array
Then create multi.sh
and try again:
$ unset foo ; declare -A foo
$ touch multi.sh
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array
NOTE: I've used printf '%s\0' *
rather than ls
because parsing the output of ls
is a bad idea. Reading a NUL-separated list of filenames will work with any valid filenames, even those containing annoying characters like newlines.
BTW, recent versions of GNU ls
have a --zero
option for NUL-separated output - I'm still not inclined to use it, I'd rather use printf ... *
as above or find
.
[[ -e multi.sh ]]
- what is your actual goal? – steeldriver Feb 14 '23 at 13:12if listcontains "${LIST}" "multi.sh"; then ...
- what you're doing now is testing whether the function outputs anything to stdout – steeldriver Feb 14 '23 at 13:32if listcontains ...
gives the desired result (if you add an answer below I'll accept it) although I still don't know why[ $(listcontains ...
does not behave as I expected. – symcbean Feb 14 '23 at 16:02ls
output for anything.ls
is a tool for interactively looking at directory metadata. Any attempts at parsingls
output with code are broken. Globs are much more simple AND correct:for file in *.txt
. Read http://mywiki.wooledge.org/ParsingLs – Gilles Quénot Feb 14 '23 at 18:19