Я пытаюсь написать сценарий bash, который считывает ввод пользователя (некоторые файлы, чтобы пользователь мог использовать завершение TAB) и копировать их в определенную папку.

#/bin/bash
read -e files
for file in $files
do
    echo $file
    cp "$file" folder/"$file"
done

Это нормально для: file1 file2 ...

Или с помощью: file* (даже если в папке есть имя файла с пробелом).

Но это не работает для имен файлов с пробелом, экранированным обратной косой чертой \ , например: file\ with\ space экранированные пробелы игнорируются, а строка разделяется на каждый пробел, даже экранированный.

Я видел информацию о цитировании, printf, IFS, чтении и пока ... Я думаю, что это очень простой сценарий bash, но я не могу найти хорошее решение. Вы можете мне помочь?

0
emile 25 Май 2016 в 02:35

4 ответа

Лучший ответ

Очистка IFS перед раскрытием без кавычек позволит продолжить подстановку, предотвращая разбиение строк:

IFS=$' \t\n' read -e -a globs  # read glob expressions into an array
IFS=''
for glob in "${globs[@]}"; do  # these aren't filenames; don't claim that they are.
  files=( $glob )              # expand the glob into filenames

  # detect the case where no files matched by checking whether the first result exists
  # these *would* need to be quoted, but [[ ]] turns off string-splitting and globbing
  [[ -e $files || -L $files ]] || {
    printf 'ERROR: Glob expression %q did not match any files!\n' "$glob" >&2
    continue
  }

  printf '%q\n' "${files[@]}"  # print one line per file matching
  cp -- "${files[@]}" folder/  # copy those files to the target
done

Обратите внимание, что мы применяем значение по умолчанию IFS=$' \t\n' во время операции read, что гарантирует, что пробелы без кавычек обрабатываются как разделитель между элементами массива на этом этапе. Позже, с files=( $glob ), напротив, у нас есть IFS='', поэтому пробелы больше не могут разделять отдельные имена.

3
Charles Duffy 25 Май 2016 в 00:51

Обратите внимание, что ответы Чарльза Даффи и user2350426 не сохраняют экранированные * s; они тоже расширят их.

Однако подход Бенджамина вообще не работает с глобусом. Он ошибается в том, что вы можете сначала поместить свои шары в строку, а затем загрузить их в массив.

Тогда он будет работать по желанию:

globs='file1 file\ 2 file-* file\* file\"\"'  # or read -re here 

# Do splitting and globbing:
shopt -s nullglob
eval "files=( $globs )"
shopt -u nullglob

# Now we can use ${files[@]}:
for file in "${files[@]}"; do
    printf "%s\n" "$file"
done

Также обратите внимание на использование nullglob для игнорирования нерасширяемых глобусов. Вы также можете использовать failglob или, для более детального управления, код, как в вышеупомянутых ответах.

Внутри функций вы, вероятно, захотите declare переменных, чтобы они оставались локальными.

0
philipp2100 15 Апр 2019 в 13:11

Вы можете прочитать имена файлов в массиве, а затем перебрать элементы массива:

read -e -a files
for file in "${files[@]}"; do
    echo "$file"
    cp "$file" folder/"$file"
done

Чтение в одну строку не будет работать независимо от того, как вы цитируете: строка будет либо разбита на каждый пробел (если она не заключена в кавычки), либо не будет вообще (если она заключена в кавычки). См. канонические вопросы и ответы для подробностей (ваш случай - последний пункт в списке).

Это предотвращает подстановку, т. Е. file* не раскрывается. Решение, которое учитывает это, см. В ответе Чарльза.

0
Community 23 Май 2017 в 12:25

Есть полнофункциональное решение для файлов и глобусов.

С помощью использования xargs (который может сохранять строки в кавычках). Но вам нужно писать файлы с пробелами внутри кавычек:

"file with spaces"

Когда вы используете сценарий: Снимите кавычки с прочитанного и процитируйте задание для listOfFiles.

Я также использую некоторые идеи в сообщении @CharlesDuffy (спасибо, Чарльз).

#!/bin/bash

# read -e listOfFiles
listOfFiles='file1 file* "file with spaces"'

IFS=''
while IFS='' read glob; do     # read each file expressions into an array
  files=( $glob )              # try to expand the glob into filenames

  # If no file match the split glob
  # Then assume that the glob is a file and test its existence
  [[ -e $files || -L $files ]] || {
      files="$glob"
      [[ -e $files || -L $files ]] || {
          printf 'ERROR: Glob "%q" did not match any file!\n' "$glob" >&2
          continue
      }
  }

  printf '%q\n' "${files[@]}"  # print one line per file matching
  cp -- "${files[@]}" folder/  # copy those files to the target
done < <(xargs -n1 <<<"$listOfFiles")
1
25 Май 2016 в 04:08