Add to array variable from pipeline command

I am writing a bash function to get all the git repositories, but I ran into a problem when I want to save all the paths to the git repository for the patharray array. Here is the code:

 gitrepo() { local opt declare -a patharray locate -b '\.git' | \ while read pathname do pathname="$(dirname ${pathname})" if [[ "${pathname}" != *.* ]]; then # Note: how to add an element to an existing Bash Array patharray=("${patharray[@]}" '\n' "${pathname}") # echo -e ${patharray[@]} fi done echo -e ${patharray[@]} } 

I want to save all the repository paths to the patharray array, but I cannot get it outside the pipeline , which consists of the locate and while command.
But I can get the array in the pipeline command, the commented command # echo -e ${patharray[@]} works well if it is not ready, so how can I solve the problem?

And I tried the export command, however it seems that it cannot pass patharray to the pipeline.

+5
arrays bash pipeline
May 14 '16 at 16:19
source share
2 answers

First of all, adding an array to a variable is best done using array[${#array[*]}]="value" or array+=("value1" "value2" "etc") if you do not want to convert the entire array (which you do not do).

Now, since pipeline commands are executed in subprocesses , changes made to the variable inside the pipeline command will not propagate outside of it. There are several options to get around this (most of them are listed in Greg BashFAQ / 024 ):

  • pass result through stdout instead

    • simplest; you will need to do all this to get the value from the function (although there are ways to return the correct variable )
    • any special characters in the paths can be reliably processed using \0 as a delimiter (see Capturing find. -print0 output to a bash array for reading \0 -separated lists)

       locate -b0 '\.git' | while read -r -d '' pathname; do dirname -z "$pathname"; done 

      or simply

       locate -b0 '\.git' | xargs -0 dirname -z 
  • avoid starting a loop in a subprocess

    • avoid the conveyor at all

      • temporary file / FIFO (bad: requires manual cleaning available to others)
      • temporary variable (mediocre: unnecessary memory overhead)
      • process substitution (a special syntax-supported FIFO case does not require manual cleaning, code adapted from Greg BashFAQ / 020 ):

         i=0 #`unset i` will error on `i' usage if the `nounset` option is set while IFS= read -r -d $'\0' file; do patharray[i++]="$(dirname "$file")" # or however you want to process each file done < <(locate -b0 '\.git') 
    • use the lastpipe parameter (new in bash 4.2) - does not run the last pipeline command in the subprocess (mediocre: has a global effect)

+2
May 14 '16 at 19:15
source share

Bash executes all pipeline commands in a separate SubShell s. When the subshell containing the while ends, all changes made to the patharray variable are lost.

You can simply group the while and echo statement together so that they are contained in the same subshell:

 gitrepo() { local pathname dir local -a patharray locate -b '\.git' | { # the grouping begins here while read pathname; do pathname=$(dirname "$pathname") if [[ "$pathname" != *.* ]]; then patharray+=( "$pathname" ) # add the element to the array fi done printf "%s\n" "${patharray[@]}" # all those quotes are needed } # the grouping ends here } 

Alternatively, you can structure your code so that it does not need a pipe: use ProcessSubstitution (For details, see the Bash manual - man bash | less +/Process\ Substitution ):

 gitrepo() { local pathname dir local -a patharray while read pathname; do pathname=$(dirname "$pathname") if [[ "$pathname" != *.* ]]; then patharray+=( "$pathname" ) # add the element to the array fi done < <(locate -b '\.git') printf "%s\n" "${patharray[@]}" # all those quotes are needed } 
+4
May 14 '16 at 16:37
source share



All Articles