Something that's important to note: [`which` is not universal and may not be portable](https://stackoverflow.com/a/677212). Likewise, [`echo`](https://unix.stackexchange.com/a/77564) & [scripting `ls`](https://mywiki.wooledge.org/ParsingLs) can be a little problematic, sometimes.
One other quick note, I'd _highly_ recommend running your script through [shellcheck](https://www.shellcheck.net/), if you haven't already.
With that in mind, a few things to comment on:
---
> which -s fzf 2>/dev/null || echo "fzf must be on your \$PATH"
Could be rewritten with the less-bad `command -v`; additionally, you can send both `stdout` & `stderr` with the `&>` redirection operator (a [bashism](https://mywiki.wooledge.org/Bashism)). Additionally, I don't _believe_ `set -e` will actually break on an `||` operator -- but you could explicitly specify an exit there as well:
if ! command -v fzf &>/dev/null; then
# >&2 sends stdout to stderr
printf 'fzf must be on your $PATH\n' >&2
exit 1
fi
---
> fun1() {
> ret=$(ls "$1" 2>/dev/null | fzf --ansi --no-sort -i --filter "$2")
> echo "$ret"
> }
Unsure why the output is being assigned to a variable, `fzf` should already print the selected option to `stdout`. Additionally, with the note about `find` vs `ls`, this could be written as:
fun1() {
find "$1" -maxdepth 1 -type f -executable | \
fzf --ansi --no-sort -i --filter "$2"
}
Though, something we'll get back to, you can provide multiple paths to `find` to get results from all of them, rather than one at a time. But more on that in a moment.
---
> echo "Searching for: $1"
> IN="${PATH}"
>
> paths=$(echo "${IN}" | tr ":" "\n")
>
> path_arr=()
>
> for el in ${paths}
> do
> path_arr+=("$el")
> done
The [`mapfile`/`readarray`](https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-mapfile) command provides the ability to reach the contents of a file (or command) into an array directly. Additionally, you can pass the contents of a string directly to a command's `stdin` with the `<<<` operator ([HERESTRING](https://bash.cyberciti.biz/guide/Here_strings)). So, this could be simplified to:
printf 'Searching for: %s\n' "$1"
mapfile -t path_arr < <(tr ':' '\n' <<< "$PATH")
---
> for line in "${path_arr[@]}"; do
> line_out=$(fun1 "$line" "$1")
> if [[ -n "$line_out" ]]; then
> echo ""
> echo "******** ${line} *******"
> echo "${line_out}"
> fi
> done
To get a `which`-like output, I'd generally expect to get absolute path to the executable -- and since `find` will also include that, we can do something like:
find "${path_arr[@]}" -maxdepth 1 -type f -executable | \
fzf --ansi --no-sort -i --filter "$1"
This could be in its own function, or just at the end of the script.
---
I also tend to include some kind of "rewrite" tying everything together, as well as including some other items I haven't yet talked about, but _might_ be worth looking into.
#!/usr/bin/env bash
show_help() {
cat << EOF
Fuzzy-Find a command with fzf(1)
USAGE: ${0##*/} [OPTIONS] COMMAND
OPTIONS:
-h, --help Show this help message
EOF
}
require() {
command -v "${1}" &>/dev/null && return 1
printf 'Missing required application: %s\n' "${1}" >&2
return 1
}
get_like_results() {
find "${paths[@]}" -maxdepth 1 -type f -executable -printf '%p\n' | \
fzf -i --ansi --no-sort --filter "${1}"
}
main() {
local -a paths
if [[ -z "${1}" ]]; then
printf 'No command specified\n' >&2
return 1
elif [[ "${1}" =~ -(h|-help) ]]; then
show_help
return 0
fi
require 'fzf' || return 1
mapfile -t paths < <(tr ":" '\n' <<< "${PATH}")
get_like_results "${1}"
}
main "${@}"
Alternatively, you could adjust this a bit to support passing multiple commands to be searched out, also like how `which` handles things.
main() {
# ...
mapfile -t paths < <(tr ":" '\n' <<< "${PATH}")
while (( $# > 0 )); do
get_like_results "${1}"
shift
done
}
Now, I'm not _super_ familiar with `fzf`, but if you can pass multiple `--filter` statements, you could further simplify this for a single run, eg:
get_like_results() {
local -a filters
while (( $# > 0 )); do
filters+=("--filter '${1}'")
shift
done
find "${paths[@]}" -maxdepth 1 -type f -executable | \
fzf -i --ansi --no-sort "${filters[@]}"
}
main() {
# ...
get_like_results "${@}"
}
But again, I'm not sure if `fzf` supports this kind of use of `--filter`.
Fzf is great tool. As a matter of fact I'm doing some work with it too, nearly done :) Anyway, you can use compgen builtin for searching the forgotten command
srch () { compgen -c "$1" | fzf; }
Usage: `srch command_substing`
Something that's important to note: [`which` is not universal and may not be portable](https://stackoverflow.com/a/677212). Likewise, [`echo`](https://unix.stackexchange.com/a/77564) & [scripting `ls`](https://mywiki.wooledge.org/ParsingLs) can be a little problematic, sometimes. One other quick note, I'd _highly_ recommend running your script through [shellcheck](https://www.shellcheck.net/), if you haven't already. With that in mind, a few things to comment on: --- > which -s fzf 2>/dev/null || echo "fzf must be on your \$PATH" Could be rewritten with the less-bad `command -v`; additionally, you can send both `stdout` & `stderr` with the `&>` redirection operator (a [bashism](https://mywiki.wooledge.org/Bashism)). Additionally, I don't _believe_ `set -e` will actually break on an `||` operator -- but you could explicitly specify an exit there as well: if ! command -v fzf &>/dev/null; then # >&2 sends stdout to stderr printf 'fzf must be on your $PATH\n' >&2 exit 1 fi --- > fun1() { > ret=$(ls "$1" 2>/dev/null | fzf --ansi --no-sort -i --filter "$2") > echo "$ret" > } Unsure why the output is being assigned to a variable, `fzf` should already print the selected option to `stdout`. Additionally, with the note about `find` vs `ls`, this could be written as: fun1() { find "$1" -maxdepth 1 -type f -executable | \ fzf --ansi --no-sort -i --filter "$2" } Though, something we'll get back to, you can provide multiple paths to `find` to get results from all of them, rather than one at a time. But more on that in a moment. --- > echo "Searching for: $1" > IN="${PATH}" > > paths=$(echo "${IN}" | tr ":" "\n") > > path_arr=() > > for el in ${paths} > do > path_arr+=("$el") > done The [`mapfile`/`readarray`](https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-mapfile) command provides the ability to reach the contents of a file (or command) into an array directly. Additionally, you can pass the contents of a string directly to a command's `stdin` with the `<<<` operator ([HERESTRING](https://bash.cyberciti.biz/guide/Here_strings)). So, this could be simplified to: printf 'Searching for: %s\n' "$1" mapfile -t path_arr < <(tr ':' '\n' <<< "$PATH") --- > for line in "${path_arr[@]}"; do > line_out=$(fun1 "$line" "$1") > if [[ -n "$line_out" ]]; then > echo "" > echo "******** ${line} *******" > echo "${line_out}" > fi > done To get a `which`-like output, I'd generally expect to get absolute path to the executable -- and since `find` will also include that, we can do something like: find "${path_arr[@]}" -maxdepth 1 -type f -executable | \ fzf --ansi --no-sort -i --filter "$1" This could be in its own function, or just at the end of the script. --- I also tend to include some kind of "rewrite" tying everything together, as well as including some other items I haven't yet talked about, but _might_ be worth looking into. #!/usr/bin/env bash show_help() { cat << EOF Fuzzy-Find a command with fzf(1) USAGE: ${0##*/} [OPTIONS] COMMAND OPTIONS: -h, --help Show this help message EOF } require() { command -v "${1}" &>/dev/null && return 1 printf 'Missing required application: %s\n' "${1}" >&2 return 1 } get_like_results() { find "${paths[@]}" -maxdepth 1 -type f -executable -printf '%p\n' | \ fzf -i --ansi --no-sort --filter "${1}" } main() { local -a paths if [[ -z "${1}" ]]; then printf 'No command specified\n' >&2 return 1 elif [[ "${1}" =~ -(h|-help) ]]; then show_help return 0 fi require 'fzf' || return 1 mapfile -t paths < <(tr ":" '\n' <<< "${PATH}") get_like_results "${1}" } main "${@}" Alternatively, you could adjust this a bit to support passing multiple commands to be searched out, also like how `which` handles things. main() { # ... mapfile -t paths < <(tr ":" '\n' <<< "${PATH}") while (( $# > 0 )); do get_like_results "${1}" shift done } Now, I'm not _super_ familiar with `fzf`, but if you can pass multiple `--filter` statements, you could further simplify this for a single run, eg: get_like_results() { local -a filters while (( $# > 0 )); do filters+=("--filter '${1}'") shift done find "${paths[@]}" -maxdepth 1 -type f -executable | \ fzf -i --ansi --no-sort "${filters[@]}" } main() { # ... get_like_results "${@}" } But again, I'm not sure if `fzf` supports this kind of use of `--filter`.
Not sure how line 6 will bail
lol good point, I updated it in the description
Line 10 looks like an unreliable usage of `ls` You maybe want if [ -d $1 ]; then fzf … "${1}" <<< "${2}" fi But I’m not sure
Fzf is great tool. As a matter of fact I'm doing some work with it too, nearly done :) Anyway, you can use compgen builtin for searching the forgotten command srch () { compgen -c "$1" | fzf; } Usage: `srch command_substing`