5

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 Commented Oct 26, 2017 at 8:27
  • Hmm, works for me as written with bash version 4.4.12 on arch linux. What are you running this on? Commented Oct 26, 2017 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
    Commented Oct 26, 2017 at 8:42
  • @krankry Why do you want to use the complex function to do what looks like a very simple operation?
    – Kusalananda
    Commented Oct 26, 2017 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
    Commented Oct 26, 2017 at 9:02

2 Answers 2

5

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. Commented Oct 26, 2017 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
    Commented Oct 27, 2017 at 20:08

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.