1
\$\begingroup\$

I've got the following shell script (let say get_includes.sh):

#!/usr/bin/env bash includes=($(grep ^#include file.c | grep -o '"[^"]\+"' | tr -d '"')) echo "0: ${includes[0]}" echo "1: ${includes[1]}" 

which aims at finding relative include files in source code file.

So for given file like this (file.c):

#include "foo.h" #include "bar.h" #include <stdio.h> int main(void) { printf("Hello World\n"); return 0; } 

It'll return the following results which are correct:

$ ./get_includes.sh 0: foo.h 1: bar.h 

The code works as expected, however shellcheck complains about the following issues:

$ shellcheck get_includes.sh In get_includes.sh line 2: includes=($(grep ^#include file.c | grep -o '"[^"]\+"' | tr -d '"')) ^-- SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting). For more information: https://www.shellcheck.net/wiki/SC2207 -- Prefer mapfile or read -a to spli... https://www.shellcheck.net/wiki/SC2236 -- Use -n instead of ! -z. 

So:

  • I can't quote command substitution, as I expect the command to expand to an array.
  • I don't want to ignore the warning, I'd like to correct it.

I'm using Bash 4.

So, how I can correct the above line to satisfy shellcheck? If possible, I'd like to keep it in one-liner.


I've tried the following approaches which failed:

$ (grep ^#include file.c | grep -o '"[^"]\+"' | read -a myarr; echo $myarr) (nothing is printed) $ (grep ^#include file.c | grep -o '"[^"]\+"' | mapfile myarr; echo $myarr) (nothing is printed) 
\$\endgroup\$

    2 Answers 2

    2
    \$\begingroup\$

    ... so what's going on?

    $ (grep ^#include file.c | grep -o '"[^"]\+"' | read -a myarr; echo $myarr) (nothing is printed) $ (grep ^#include file.c | grep -o '"[^"]\+"' | mapfile myarr; echo $myarr) (nothing is printed) 

    Why doesn't this work? Because "each command in a pipeline is executed in its own subshell". As the page explains, you could enable the lastpipe option to have the last element of the pipeline run in the current process. (The page doesn't mention that this will only work when you use it in a script, where job control is not active (as mentioned here). It won't work in an interactive shell.)

    Keep pipelines as short as possible

    It's good to use the fewest possible processes in pipelines. Multiple grep in a pipeline are points to look at with suspicion. You can make the first grep work a little harder, using a stricter pattern, and then you can achieve the same result with 2 processes instead of 3:

    grep '^#include .*"' file.c | cut -d'"' -f2 
    \$\endgroup\$
    1
    • \$\begingroup\$What would have worked is ... | { read -a myarr; echo "$myarr"; } but it would have made for quite ugly code.\$\endgroup\$CommentedMar 25, 2019 at 8:10
    0
    \$\begingroup\$

    After reading more about SC2207, the proper syntax would be:

    mapfile -t includes < <(grep ^#include file.c | grep -o '"[^"]\+"' | tr -d '"') 
    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.