Bash – How to call a bash function in bash script inside awk

awkbashfunction

This issue is related to Using bash shell function inside AWK

I have this code

#!/bin/bash

function emotion() {
            #here is function code end with return value...
            echo $1
}

export -f emotion

#I've put all animals in array
animalList=($(awk '{print $1}' animal.csv)) 

#loop  array and grep all the lines form the file
for j in ${animalList[@]}
do
  :                                                     #here I'am running a bash script calling emotion function 
   grep $j animal.csv | awk '{for(i=2;i<=NF;i++){system("bash -c '\''emotion "$i"'\''")}}'
done

and I have this file:

cat    smile    happy   laugh
dog    angry    sad
mouse  happy    
wolf   sad      cry
fox    sleep    quiet 

The output should like this:

smile
happy
laugh
angry
sad
happy    
sad
cry
sleep
quiet 

The issue it tells me bash: emotion: command not found

According to akarilimano's comment here

this is not working on my Ubuntu 16.04. This is strange, because it used to work "on Ubuntu 14.04.

So how to do it in newer versions?

Best Answer

That's probably not the best way to approach the problem.

From awk, all you can do is build a command line that system() passes to sh. So, you need the arguments to be formatted in the sh syntax.

So you'd need:

emotion() {
  echo "$i"
}
export -f emotion
awk -v q="'" '
  function sh_quote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  {
    for (i = 2; i <= NF; i++)
      status = system("bash -c '\''emotion \"$@\"'\'' bash " sh_quote($1)
  }'

Here quoting awk's $1 so it can be safely embedded in the sh command line that ends up running bash with the content of $1 as last argument, which then passes it to emotion.

That assumes your sh and your awk don't strip the special environment variables that bash uses to export functions (like pdksh and derivatives (such as mksh) do for instance, or dash since 0.5.8 which explains your 14.04 vs 16.04 issue), and that your distribution has not disabled exported functions in bash.

If it does, you could do it like for ksh/zsh, and pass the definition of the function some other way, like:

CODE=$(typeset -f emotion) awk -v q="'" '
  function sh_quote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  {
    for (i = 2; i <= NF; i++)
      status = system("bash -c '\''eval \"$CODE\"; emotion \"$@\"'\'' bash " \
                      sh_quote($1)
  }'

In both cases, that means running one sh and one bash for it. Maybe you can pass the $i to bash some other way than via a system() that executes two instances of a shell each time. Like:

awk '{for (i=2; i<=NF; i++) printf "%s\0" $i}' |
  while IFS= read -r i; do
    emotion "$i"
  done

Or do the word splitting in bash directly:

unset IFS
while read -ra fields; do
  for i in "${fields[@]:1}"; do
    emotion "$i"
  done
done
Related Question