bash: безопасное для пробелов процедурное использование find в select

tee может это сделать:

docker exec -i my_container tee file < file_on_host

-i необходим для работы stdin.

Он также выведет файл, чего можно избежать, перенаправив на хост / dev / null :

docker exec -i my_container tee file < file_on_host > /dev/null
12
13.07.2017, 19:05
4 ответа

Если вам нужно обрабатывать только пробелы и табуляции (, а не встроенные символы новой строки ), вы можете использоватьmapfile(или его синонимreadarray)для чтения в массив, например. дано

$ ls -1
file
other file
somefile

, затем

$ IFS= mapfile -t files < <(find. -type f)
$ select f in "${files[@]}"; do ls "$f"; break; done
1)./file
2)./somefile
3)./other file
#? 3
./other file

Если вам действительно нужно обрабатывать новые строки, а ваша bashверсия предоставляет -нуль-разделительmapfile1 , вы можете изменить это на IFS= mapfile -t -d '' files < <(find. -type f -print0). В противном случае соберите эквивалентный массив из вывода findс нулевым разделителем -, используя цикл read:

.
$ touch $'filename\nwith\nnewlines'
$ 
$ files=()
$ while IFS= read -r -d '' f; do files+=("$f"); done < <(find. -type f -print0)
$ 
$ select f in "${files[@]}"; do ls "$f"; break; done
1)./file
2)./somefile
3)./other file
4)./filename
with
newlines
#? 4
./filename?with?newlines

1 опция -dбыла добавлена ​​к mapfileв bashверсии 4.4 iirc

15
27.01.2020, 19:54

Вы не можете установить переменную перед циклической конструкцией, но вы можете установить ее перед условием. Вот фрагмент из справочной страницы:

The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described above in PARAMETERS.

(Цикл не является простой командой .)

Вот часто используемая конструкция, демонстрирующая сценарии неудачи и успеха:

IFS=$'\n' while read -r x; do...; done </tmp/file     # Failure
while IFS=$'\n' read -r x; do...; done </tmp/file     # Success

К сожалению, я не вижу способа внедрить измененный IFSв конструкцию select, в то время как он влияет на обработку связанного $(...). Однако ничто не мешает установить IFSвне цикла :

.
IFS=$'\n'; while read -r x; do...; done </tmp/file    # Also success

и я вижу, что эта конструкция работает сselect:

IFS=$'\n'; select file in $(find -type f -name 'file*'); do echo "$file"; break; done

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

OIFS="$IFS" IFS=$'\n'                     # Split on newline only
OSHELLOPTS="$SHELLOPTS"; set -o noglob    # Wildcards must not expand twice

select file in $(find -type f -name 'file*'); do echo $file; break; done

IFS="$OIFS"
[[ "$OSHELLOPTS" !~ noglob ]] && set +o noglob
6
27.01.2020, 19:54

Возможно, я не в своей юрисдикции, но, может быть, вы можете начать с чего-то вроде этого, по крайней мере, у него нет проблем с пробелами:

find -maxdepth 1 -type f -printf '%f\000' | {
    while read -d $'\000'; do
            echo "$REPLY"
            echo
    done
}

Чтобы избежать любых возможных ложных предположений, как отмечено в комментариях, имейте в виду, что приведенный выше код эквивалентен:

   find -maxdepth 1 -type f -printf '%f\0' | {
        while read -d ''; do
                echo "$REPLY"
                echo
        done
    }
4
27.01.2020, 19:54

В этом ответе есть решения для любого типа файлов. С новыми строками или пробелами.
Есть решения для недавнего bash, а также для древнего bash и даже для старых оболочек posix.

Дерево, указанное ниже в этом ответе [1] , используется для тестов.

выберите

Легко заставить selectработать либо с массивом:

$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done

Или с позиционными параметрами:

$ set -- "$dir"/*
$ select var; do echo "$var"; break; done

Таким образом, единственная реальная проблема состоит в том, чтобы получить «список файлов» (, правильно разделенный )внутри массива или внутри позиционных параметров. Продолжай читать.

баш

Я не вижу проблемы с bash, о которой вы сообщаете. Bash может выполнять поиск внутри заданного каталога:

$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

Или, если вам нравится петля:

$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

Обратите внимание, что приведенный выше синтаксис будет корректно работать с любой (разумной )оболочкой (не csh по крайней мере ).

Единственное ограничение приведенного выше синтаксиса — переход в другие каталоги.
Но Баш мог это сделать:

$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

Чтобы выбрать только некоторые файлы (, подобные тем, которые заканчиваются в файле ), просто замените*:

$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>

прочный

Когда вы размещаете в заголовке «пространство-сейф », я предполагаю, что вы имели в виду «надежный ».

Самый простой способ быть устойчивым к пробелам (или символам новой строки )— отказаться от обработки ввода, содержащего пробелы (или символы новой строки ).Очень простой способ сделать это в оболочке — выйти с ошибкой, если любое имя файла расширяется пробелом. Есть несколько способов сделать это, но наиболее компактным (и posix )(, но ограниченным содержимым одного каталога, включая имена suddirectories и избегая точечных -файлов ), является:

$ set -- "$dir"/file*                            # read the directory
$ a="$(printf '%s' "$@" x)"                      # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space"  # if $a has an space.
$ nl='
'                    # define a new line in the usual posix way.  

$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline"  # if $a has a newline.

Если используемое решение надежно в любом из этих пунктов, удалите тест.

В bash вложенные каталоги -можно было протестировать сразу с помощью описанного выше **.

Существует несколько способов включения точечных файлов, решение Posix:

set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*

найти

Если по какой-либо причине необходимо использовать find, замените разделитель на NUL (0x00 ).

Баш 4.4+

$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>

Баш 2.05+

i=1  # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do 
    arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"

POSIXLY

Чтобы сделать правильное решение POSIX, в котором find не имеет разделителя NUL и нет ни -d(, ни-a)для чтения, нам нужен совершенно другой подход.

Нужно использовать комплекс -execfrom find с вызовом шелла:

find "$dir" -type f -exec sh -c '
    for f do
        echo "<$f>"
    done
    ' sh {} +

Или, если требуется select (select является частью bash, а не sh):

$ find "$dir" -type f -exec bash -c '
      select f; do echo "<$f>"; break; done ' bash {} +

1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>

[1] Это дерево (\012 — это новые строки):

$ tree
.
└── deep
    └── inside
        └── a
            └── dir
                ├── directory
                │   ├── file
                │   ├── file name
                │   └── file with a \012newline
                ├── file
                ├── file name
                ├── otherfile
                ├── with a\012newline
                └── zz last file

Можно построить с помощью этих двух команд:

$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}
8
27.01.2020, 19:54

Теги

Похожие вопросы