Как я могу получить первый матч от расширения подстановочных знаков?

Оболочки, такие как Bash и Zsh, расширяют подстановочные знаки в аргументах, так как многие аргументы соответствуют шаблону:

$ echo *.txt
1.txt 2.txt 3.txt

Но что, если мне нужно только вернуть первый матч, а не все совпадения?

$ echo *.txt
1.txt

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

27 голосов | спросил Flimm 18 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 18 Sep 2014 13:29:16 +0400 2014, 13:29:16

5 ответов


20

Одним из надежных способов в bash является расширение в массив и вывод только первого элемента:

pattern="*.txt"
files=( $pattern )
echo "${files[0]}"  # printf is safer!

(Вы можете даже просто echo $files, отсутствующий индекс обрабатывается как [0].)

Это безопасно обрабатывает пробел /табу /новую строку и другие метасимволы при расширении имен файлов. Обратите внимание, что настройки локали могут влиять на то, что «первый».

Вы также можете сделать это в интерактивном режиме с помощью функции завершения bash :

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}   # string to expand

    if compgen -G "$cur*" > /dev/null; then
        local files=( ${cur:+$cur*} )   # don't expand empty input as *
        [ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
    fi
}
complete -o bashdefault -F _echo echo

Это связывает функцию _echo для завершения аргументов в echo (переопределение нормального завершения). Дополнительный «*» добавляется в код выше, вы можете просто нажать вкладку на частичном имени файла, и, надеюсь, что-то произойдет правильно.

Код слегка свернут, а не устанавливает или принимает nullglob (shopt -s nullglob), мы проверяем compgen -G можем развернуть glob до некоторых совпадений, затем мы благополучно расширяем массив и, наконец, устанавливаем COMPREPLY, чтобы цитирование было надежным.

Вы можете частично сделать это (программно развернуть glob) с помощью compgen -G, но он не является надежным поскольку он выводит без кавычек в стандартный вывод.

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

Вы также можете просто использовать compgen вне функции завершения:

files=( $(compgen -W "$pattern") )

Следует отметить, что «~» - это не глобус, он обрабатывается bash на отдельном этапе расширения, как и переменные $ и другие расширения. compgen -G просто делает имя файла globbing, но compgen -W дает вам все расширения по умолчанию bash, хотя, возможно, слишком много расширений (включая `` и $()). В отличие от -G, -W безопасно цитируется (я не могу объяснить несоответствие). Поскольку целью -W является то, что он расширяет токены, это означает, что он расширит «a» до «a», даже если такой файл не существует , поэтому, возможно, это не идеально.

Это легче понять, но может иметь нежелательные побочные эффекты:

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local files=( $(compgen -W "$cur") ) 
    printf -v COMPREPLY %q "${files[0]}"  
}

Тогда:

touch $'curious \n filename'

echo curious* вкладка

Обратите внимание на использование printf %q, чтобы безопасно процитировать значения.

Один из окончательных вариантов - использовать вывод с 0 -разделом с утилитами GNU (см. FAQ по bash ):

pattern="*.txt"
while IFS= read -r -d $'\0' filename; do 
    printf '%q' "$filename"; 
    break; 
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )

Этот параметр дает вам немного больше контроля над порядком сортировки (порядок при расширении glob будет подчиняться вашей локали /LC_COLLATE и может или не может сбрасывать футляр), но в остальном это довольно большой молот для такой небольшой проблемы; -)

ответил mr.spuratic 18 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 18 Sep 2014 20:48:27 +0400 2014, 20:48:27
15

В zsh используйте [1] классификатор glob . Обратите внимание, что хотя этот специальный случай возвращает не более одного совпадения, он по-прежнему является списком, и глобусы не расширяются в контекстах, которые ожидают одно слово, такое как присваивания (в отличие от назначений массивов).

echo *.txt([1])

В ksh или bash вы можете заполнить весь список совпадений в массиве и использовать первый элемент.

tmp=(*.txt)
echo "${tmp[0]}"

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

set -- *.txt
echo "$1"

Это сжимает позиционные параметры. Если вы этого не хотите, вы можете использовать подоболочку.

echo "$(set -- *.txt; echo "$1")"

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

set_to_first () {
  eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"
ответил Gilles 19 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 19 Sep 2014 02:09:51 +0400 2014, 02:09:51
5

Try:

for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt

Заметка о том, что расширение имени файла сортируется в соответствии с последовательностью сортировки, действующей в текущей локали.

ответил cuonglm 18 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 18 Sep 2014 13:38:20 +0400 2014, 13:38:20
2

Простое решение:

 sh -c 'echo "$1"' sh *.txt

Или используйте printf, если хотите.

ответил G-Man 18 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 18 Sep 2014 23:26:13 +0400 2014, 23:26:13
0

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

echo $(ls *.txt | head -n1)

Вы можете, конечно, заменить head на tail и -n1 с любым другим номером.

ответил user149485 30 AM00000010000001531 2018, 01:28:15

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132