список файлов bash iterate, за исключением случаев, когда он пуст


33

Я думал, что это будет просто - но это оказывается сложнее, чем я ожидал.

Я хочу перебрать все файлы определенного типа в каталоге, поэтому я пишу это:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

Это работает до тех пор, пока в каталоге есть хотя бы один соответствующий файл . Однако, если нет подходящих файлов, я получаю это:

current file is *.zip

Я тогда попробовал:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

Хотя тело цикла не выполняется, когда нет файлов, я получаю сообщение об ошибке от ls:

ls: *.zip: No such file or directory

Как мне написать цикл, который чисто обрабатывает не соответствующие файлы?


7
Добавьте shopt -s nullglobперед запуском цикла for.
cuonglm

@cuolnglm: жутко это приводит к тому, что ls возвращает имя исполняемого скрипта, а не пустой список в этом поле RHEL5 (bash 3.2.25), если я выполняю FILES=ls * .zip, ; for fname in "${FILES}"...но он работает, как и ожидалось, сfor fname in *.zip ; do....
symcbean

3
Используйте for file in *.zip, нет `ls ...`. Предложение @ cuonglm таково, что *.zipрасширяется до нуля, когда шаблон не соответствует ни одному файлу. lsбез аргументов перечисляет текущий каталог.
Стефан Шазелас

1
В этом вопросе обсуждается, почему lsвообще следует избегать синтаксического анализа выходных данных : почему бы неls выполнить синтаксический анализ ? ; также смотрите ссылку в верхней части этой страницы на статью BashGuide's ParsingLs .
PM 2Ring

Ответы:


34

В bash, вы можете установить nullglobопцию так, чтобы шаблон, который ничего не соответствует, «исчезал», а не рассматривался как литеральная строка:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

В сценарии оболочки POSIX вы просто проверяете, что fnameсуществует (и в то же время [ -f ]проверяете, что это обычный файл (или символическая ссылка на обычный файл), а не другие типы, такие как directory / fifo / device ...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

Замените [ -f "$fname" ]на, [ -e "$fname" ] || [ -L "$fname ]если вы хотите перебрать все (не скрытые) файлы, имя которых заканчивается .zipнезависимо от их типа.

Замените *.zipна, .*.zip .zip *.zipесли вы также хотите рассмотреть скрытые файлы, имя которых заканчивается на .zip.


1
shopt -s nullglobне работал у меня на Ubuntu 17.04, но [ -f "$fname" ] || continueработал хорошо.
Коппор

@koppor Похоже, вы на самом деле не используете bash.
Чепнер

2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

В комментарии здесь вы упоминаете вызов функции ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *

2

Используйте найти

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

Вы ДОЛЖНЫ экспортировать свою функцию оболочки, export -fчтобы это работало. findВыполняет теперь, bashчто выполняет вашу функцию оболочки, и остается только на текущем уровне каталога.


Который проходит через подкаталоги, и я хочу вызвать функцию совпадений (не скрипт) для совпадений.
Symcbean

@symcbean Я редактировал, чтобы ограничиться одним
директором

-3

Вместо того:

FILES=`ls *.zip`

Пытаться:

FILES=`ls * | grep *.zip`

Таким образом, если ls завершится неудачно (что и происходит в вашем случае), он выдаст сбойный вывод и вернется в виде пустой переменной.

current file is      <---Blank Here

Вы можете добавить к этому логику, чтобы она возвращала «Файл не найден»

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

Таким образом, если предыдущая команда была выполнена успешно (выход со значением 0), она напечатает текущий файл, в противном случае будет напечатано «Файлы не найдены»


1
Я думаю, что было бы плохой идеей добавить еще один процесс ( grep) вместо того, чтобы пытаться исправить проблему, используя лучший инструмент ( find) или изменив соответствующие настройки для текущего решения (с shopt -s nullglob)
Томас Барухель

1
Согласно комментарию ОП на их оригинальном посту shopt -s nullglob, не работает. Я пытался findпроверить мой ответ, и он продолжал терпеть неудачу. Я думаю из-за экспорта, что сказал Дани.
Кип К
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.