代码之家  ›  专栏  ›  技术社区  ›  James Newton

bash:将find与多个文件类型一起使用,作为数组提供

  •  2
  • James Newton  · 技术社区  · 7 年前

    在bash函数中,我想列出给定文件夹中与给定文件类型集相对应的所有文件。在伪代码中,我想象这样的事情:

    getMatchingFiles() {
      output=$1
      directory=$2
      shift 2
      _types_=("$@")
    
      file_array=find $directory -type f where-name-matches-item-in-_types_
    
      # do other stuff with $file_array, such as trimming file names to
      # just the basename with no extension
    
      eval $output="${file_array[@]}"
    }
    
    dir=/path/to/folder
    types=(ogg mp3)
    getMatchingFiles result dir types
    echo "${result[@]}"
    

    有趣的是,基于我目前对bash的了解,这里有多种变通方法,我正在使用它们来实现这一点。我对函数返回文件数组的方式有一个问题:最后一个命令尝试执行每个文件,而不是设置输出参数。

    getMatchingFiles() {
      local _output=$1
      local _dir=$2
      shift 2
      local _type=("$@")
      local _files=($_dir/$_type/*)
      local -i ii=${#_files[@]}
      local -a _filetypes
      local _file _regex
    
      case $_type in
        audio )
          _filetypes=(ogg mp3)
          ;;
        images )
          _filetypes=(jpg png)
          ;;
      esac
    
      _regex="^.*\.("
      for _filetype in "${_filetypes[@]}"
      do
         _regex+=$_filetype"|"
      done
    
      _regex=${_regex:0:-1}
      _regex+=")$"
    
      for (( ; ii-- ; ))
      do
        _file=${_files[$ii]}
        if ! [[ $_file =~ $_regex ]];then
          unset _files[ii]
        fi
      done
    
      echo "${_files[@]}"
    
      # eval $_output="${_files[@]}" # tries to execute the files
    }
    
    dir=/path/to/parent
    getMatchingFiles result $dir audio
    echo "${result[@]}"
    
    3 回复  |  直到 7 年前
        1
  •  2
  •   PesaThe    7 年前

    事实上,可以使用 nameref (请注意,您需要 bash 4.3或更高版本)引用数组。如果你想把输出 find 对于由名称指定的数组,可以这样引用它:

    #!/usr/bin/env bash
    
    getMatchingFiles() {
    
       local -n output=$1
       local dir=$2
       shift 2
       local types=("$@")
       local ext file
       local -a find_ext
    
       [[ ${#types[@]} -eq 0 ]] && return 1
    
       for ext in "${types[@]}"; do
          find_ext+=(-o -name "*.${ext}")
       done
    
       unset 'find_ext[0]'
       output=()
    
       while IFS=  read -r -d $'\0' file; do
          output+=("$file") 
       done < <(find "$dir" -type f \( "${find_ext[@]}" \) -print0)
    }
    
    dir=/some/path
    
    getMatchingFiles result "$dir" mp3 txt
    printf '%s\n' "${result[@]}"
    
    getMatchingFiles other_result /some/other/path txt
    printf '%s\n' "${other_result[@]}"
    

    不要传递变量 $dir 作为引用,将其作为值传递。你也可以传递文字。

        2
  •  0
  •   Renaud Pacalet    7 年前

    更新:nameref确实可以是数组 (见PesaThe的回答)

    文件名和目录名中没有空格

    我首先假设您的文件名和目录名中没有空格。如果文件名和目录名中有空格,请参阅此答案的第二部分。

    为了通过 result , dir types 根据函数的名称,您需要使用namerefs( local -n declare -n ,仅在最新版本的bash中可用)。

    另一个困难是根据您传递的类型构建find命令,但这不是一个主要问题。模式替换可以做到这一点。总之,像这样的事情应该满足你的需要:

    #!/usr/bin/env bash
    
    getMatchingFiles() {
        local -n output=$1
        local -n directory=$2
        local -n _types_=$3
        local filter
    
        filter="${_types_[@]/#/ -o -name *.}"
        filter="${filter# -o }"
        output=( $( find "$directory" -type f \( $filter \) ) )
    
        # do other stuff with $output, such as trimming file names to
        # just the basename with no extension
    }
    
    declare dir
    declare -a types
    declare -a result=()
    
    dir=/path/to/folder
    types=(ogg mp3)
    getMatchingFiles result dir types
    for f in "${result[@]}"; do echo "$f"; done
    

    文件名和目录名中有空格(但不在文件后缀中)

    如果文件名和目录名中有空格,事情就有点困难了,因为必须分配数组,使名称不被拆分成文字;这样做的一种可能性是使用 \0 由于使用了 -print0 选择 find -d $'\0' 选择 read :

    #!/usr/bin/env bash
    
    getMatchingFiles() {
        local -n output=$1
        local -n directory=$2
        local -n _types_=$3
        local filter
    
        filter="${_types_[@]/#/ -o -name *.}"
        filter="${filter# -o }"
        while read -d $'\0' file; do
            output+=( "$file" )
        done < <( find "$directory" -type f \( $filter \) -print0 )
    
        # do other stuff with $output, such as trimming file names to
        # just the basename with no extension
    }
    
    declare dir
    declare -a types
    declare -a result=()
    
    dir=/path/to/folder
    types=(ogg mp3)
    getMatchingFiles result dir types[@]
    for f in "${result[@]}"; do echo "$f"; done
    

    文件名和目录名中包含空格,甚至在文件后缀中

    好吧,你应该得到发生在你身上的一切。。。仍有可能,但作为练习。

        3
  •  0
  •   Charles Duffy    7 年前

    支持原始的、未修改的调用约定,并正确处理带有空格或全局字符的扩展:

    #!/usr/bin/env bash
    
    getMatchingFiles() {
      declare -g -a "$1=()"
      declare -n gMF_result="$1"  # variables are namespaced to avoid conflicts w/ targets
      declare -n gMF_dir="$2"
      declare -n gMF_types="$3"
      local gMF_args=( -false )   # empty type list not a special case
      local gMF_type gMF_item
    
      for gMF_type in "${gMF_types[@]}"; do
        gMF_args+=( -o -name "*.$gMF_type" )
      done
    
      while IFS= read -r -d '' gMF_item; do
        gMF_result+=( "$gMF_item" )
      done < <(find "$gMF_dir" '(' "${gMF_args[@]}" ')' -print0)
    }
    
    dir=/path/to/folder
    types=(ogg mp3)
    getMatchingFiles result dir types