3

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?

7
  • 1
    Why all this? Off the top of my head: awk '{$1=""; for(i=1;i<=NF;i++) printf("%s\n", $i) }' yourfile should get the desired result – Valentin Bajrami Oct 26 '17 at 8:27
  • Hmm, works for me as written with bash version 4.4.12 on arch linux. What are you running this on? – Michael Daffin Oct 26 '17 at 8:36
  • @val0x00ff I'm using a complex function in my code. I adjust the code in my question to make it simple – krankry Oct 26 '17 at 8:42
  • @krankry Why do you want to use the complex function to do what looks like a very simple operation? – Kusalananda Oct 26 '17 at 8:47
  • @Kusalananda There an if statement in the function has many options. It should call a url and extract some info from each emotion, and get the max value, to do more operation with the max value, and return. That's why I'm using function. I have not wrote the whole function because it's useless in the question – krankry Oct 26 '17 at 9:02
4

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
0

Your script does not work on Ubuntu 16.04 because /bin/sh is linked to the dash shell, whereas on Ubuntu 14.04 /bin/sh is linked to the bash shell. Why does this matter? Awk's system() function uses /bin/sh and therefore invokes the dash shell on Ubuntu 16.04.

2
  • dash is sh by default on 14.04 as well. The difference is that since 0.5.8, dash sanitizes its environment, so removes the BASH_FUNC_emotion%% variable (the new variable names since shellshock) from it as it doesn't consider it a valid variable name. mksh does that as well. Other shells don't in my tests. – Stéphane Chazelas Oct 26 '17 at 13:41
  • Not on all Ubuntu 14.04. For example, OOTB, bash was the default shell on the initial Linux Subsystem for Windows 10 Anniversary edition. – fpmurphy Oct 27 '17 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.