Я пытаюсь написать сценарий 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, но я не могу найти хорошее решение. Вы можете мне помочь?
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=''
, поэтому пробелы больше не могут разделять отдельные имена.
Обратите внимание, что ответы Чарльза Даффи и 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
переменных, чтобы они оставались локальными.
Вы можете прочитать имена файлов в массиве, а затем перебрать элементы массива:
read -e -a files
for file in "${files[@]}"; do
echo "$file"
cp "$file" folder/"$file"
done
Чтение в одну строку не будет работать независимо от того, как вы цитируете: строка будет либо разбита на каждый пробел (если она не заключена в кавычки), либо не будет вообще (если она заключена в кавычки). См. канонические вопросы и ответы a> для подробностей (ваш случай - последний пункт в списке).
Это предотвращает подстановку, т. Е. file*
не раскрывается. Решение, которое учитывает это, см. В ответе Чарльза.
Есть полнофункциональное решение для файлов и глобусов.
С помощью использования 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")
Похожие вопросы
Новые вопросы
bash
Этот тег предназначен для вопросов о сценариях, написанных для командной оболочки Bash. Сценарии оболочки с синтаксисом или другими ошибками, пожалуйста, проверьте их на https://shellcheck.net, прежде чем публиковать здесь. Вопросы об интерактивном использовании Bash, скорее всего, будут актуальны на Unix & Linux Stack Exchange или Super User, чем на Stack Overflow.