2

Assume there is a shell history record similar to below in your history file:

find . -type f | x grep 'linux' | wc -l

Note: x is alias of xargs command.

I want to write a shell function that get above text as parameter and return used command's full paths.

Return value for this text should be an array of command paths: ("/usr/bin/find" "/usr/bin/xargs" "/usr/bin/wc")

If I give "whereis where" as parameter to the function, where is a shell built-in so there is not path of it. Function should be return similar to ("/usr/bin/where", "where").

I think I can do this with apply regular exprations to text, but I know less regex and not much familiar with awk.

Update:

Example input and output:

$ exctractCommands "find . -type f | x grep 'linux' | wc -l; where ls"
/usr/bin/find 
/usr/bin/xargs
/usr/bin/wc
where

Could you please help me for how to write this function ?

HamZa
  • 14,671
  • 11
  • 54
  • 75
Mesut Tasci
  • 2,970
  • 1
  • 30
  • 36

3 Answers3

1

A little confused with how you want output to look, but should be easy enough to change from below script (if you want just command paths/aliases, just change out="$i" to out=""). Note, bash isn't particularly good at handling aliases in shell scripts, so you have to source whatever files you keep them in.

#!/bin/bash

ali() {
  arg="$*"
  input=$(echo "$arg"| tr ' ' '\n')
  save=""
  while read i; do
    out=$(type "$i" 2>/dev/null)
    if [[ $out == *"aliased to"* ]]; then
      out=${out%%\'*}
      out=${out##*\`}
      out=$(ali "$out")
    elif [[ $out == *"$i is"* && $out != *"builtin"* && $out != *"keyword"* ]]; then
      out=${out##*"$i is"}
    else
      out="$i"
    fi
    save="$save $out"
  done <<< "$input"
  echo "$save"  
}

shopt -s expand_aliases
source ~/.bashrc

ali "$1"

Example output

$ ./script "find . -type f | x grep 'linux' | wc -l"
 /usr/bin/find . -type f | /usr/bin/xargs  /bin/grep 'linux' |  /usr/bin/wc -l
$ ./script "[[ -f test.txt ]] && ls"
[[ -f test.txt ]] &&  /bin/ls 
$ ./script ":> test.txt"
:> test.txt
$ ./script "ll"
/bin/ls -lhtr 

Some bug with spacing/escaping somewhere, but should be easy enough to fix with an sed, or just echo -e $(./script "whatever") should work here.

Example output with out="" instead of out="$i" and hackish spacing fix

$ echo -e $(./script "find . -type f | x grep 'linux' | wc -l")
/usr/bin/find /usr/bin/xargs /bin/grep /usr/bin/wc
$ echo -e $(./script "[[ -f test.txt ]] && ls")
/bin/ls 
$ echo -e $(./script ":> test.txt")

$ echo -e $(./script "ll")
/bin/ls 

Update Exact output you want shouldn't be too hard to change in-script. But simpler, change out="$i" to out="" and do (or make a wrapper to do it). Also note, I added save="" to the script above, since there was a slight bug with $save getting kept somewhere and the first argument repeated.

$ echo -e $(./script "find . -type f | x grep 'linux' | wc -l") | tr ' ' '\n'
/usr/bin/find
/usr/bin/xargs
/bin/grep
/usr/bin/wc

$ echo -e $(./test.sh "find . -type f | x grep 'linux' | wc -l; where ls") | tr ' ' '\n'
/usr/bin/find
/usr/bin/xargs
/bin/grep
/usr/bin/wc
/bin/ls
Reinstate Monica Please
  • 11,123
  • 3
  • 27
  • 48
  • I add example input and output to question. – Mesut Tasci Jan 17 '14 at 09:46
  • @mesuutt See update (I don't have `where` on my system) – Reinstate Monica Please Jan 17 '14 at 13:59
  • I am running your script with bash on arch linux but gives me error. [See output](http://pastebin.com/M0d7KpTV) – Mesut Tasci Jan 17 '14 at 14:11
  • @mesuutt Are you sure you're not missing the `2> /dev/null` part. Also are you running it from bash? – Reinstate Monica Please Jan 17 '14 at 15:39
  • Sorry, I worked it in zsh instead bash. Now I worked it in bash and it works without error. [I am getting this output](http://pastebin.com/aM6XxK3s). But this is not what I want exactly. I want to get only command path or get name if command is a builtin or user defined function. This is possible ? – Mesut Tasci Jan 17 '14 at 16:09
  • @mesuutt Yes, see my last example output. To get this, just change `out="$i"` to `out=""`. And do the `echo -e $(./script "find ls or whatever commands") | tr ' ' '\n'`. It won't include bash builtins by default, but you can do that by adding `elif [[ $out == *"builtin"* ]]; then out="$i"` before the other `elif` line. – Reinstate Monica Please Jan 17 '14 at 17:50
  • Function entering infinite loop for this alias: `alias ls="ls --color"` because function calling itself for each part of commands. How can I solve this? – Mesut Tasci Feb 25 '14 at 14:41
0

What you may try is to identify the executables in your command first. That one you could do by finding out which tokens exist in the $PATH Variable. (They must be there since otherwise you could not run the command) Note that this part can get tricky when you have arguments in your command which themselves are valid names of executables in your path.

Afterwards you can run 'which' for every executable. 'which' itself is a unix tool. See 'man which' for explanation.

For identifying aliases you can also simply use which since 'which [somealias]' will return nothing.

ben
  • 5,671
  • 4
  • 27
  • 55
  • You should avoid `which` in bash for the reasons listed here http://stackoverflow.com/questions/592620/how-to-check-if-a-program-exists-from-a-bash-script . Though it would be fine for `zsh` (also tagged), since it's actually a builtin there. – Reinstate Monica Please Jan 17 '14 at 04:04
0

A bash answer

# split the pipeline. 
# note this is insufficient: also need to split on ; && ||
# and be aware that the first char in the command might be ( or {
IFS='|' read -ra cmds << 'END'
find . -type f | x grep 'linux' | wc -l
END


for cmd in "${cmds[@]}"; do 
    set -- $cmd
    case $(type -t $1) in
        file) type $1 ;;
        alias) [[ $(alias $1) =~ =\'([^\'[:blank:]]+) ]] && 
               type ${BASH_REMATCH[1]}
               ;; 
    esac
done
find is /usr/bin/find
xargs is /usr/bin/xargs
wc is /usr/bin/wc
glenn jackman
  • 238,783
  • 38
  • 220
  • 352