Як я можу отримати перший матч від розширення підстановки?


39

Оболонки типу Bash і Zsh розширюють підстановку на аргументи, стільки ж аргументів відповідають шаблону:

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

Але що робити, якщо я хочу лише повернути перший матч, а не всі?

$ echo *.txt
1.txt

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


ls * .txt | голова -1?
Архемар

1
@Archemar: не працює з новими рядками у назви файлів.
Flimm

Відповіді:


26

Один надійний спосіб 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може розширити глобус на деякі збіги, потім ми безпечно розширимось у масив і, нарешті, встановимо КОМПЛЕКТНО, щоб цитування надійне.

Ви можете частково це зробити (програмно розгорнути глобус) за допомогою bash's compgen -G, але це не надійно, оскільки виводить без котирування stdout.

Як завжди, завершення є досить загрозливим, це порушує завершення інших речей, включаючи змінні середовища ( детальну інформацію про імітацію поведінки за замовчуванням див. У цій _bash_def_completion()функції тут ).

Ви також можете просто використовувати compgenпоза функцією завершення:

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

Слід зазначити, що "~" не є глобусом, він обробляється bash на окремому етапі розширення, як і $ змінні та інші розширення. compgen -Gпросто робить глобус імен файлів, але 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*tab

Зверніть увагу на використання 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 )

Цей параметр дає вам трохи більше контролю над порядком сортування (порядок розширення глобуса буде залежати від вашої мови / LC_COLLATEі може, а може, і не складе), але в іншому випадку це досить великий молоток для такої невеликої проблеми ;-)


21

У 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"

1
І щоб отримати перші $ n $ матчі, ви можете використовувати*.txt([1,n])
Емре

6

Спробуйте:

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

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



1

Я просто натрапив на це старе питання, дивуючись одне і те ж. Я закінчився цим:

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

Можна, звичайно, замінити headз tailі -n1з будь-яким іншим номером.


Вище не буде працювати, якщо ви працюєте з файлами, у яких є імена нових рядків. Для роботи з новими рядками ви можете скористатися будь-яким із наведених нижче:

  • ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g' (Не працює на BSD)
  • ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
  • ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
  • echo -e "$(ls -b *.txt | head -n1)" (Працює з будь-яким особливим персонажем)

3
Ні, це не вдасться, якщо ім'я файлу має нові рядки.
Ісаак

7
в якому божевільному світі ми живемо, де в назви файлів містяться нові рядки?
billynoah

-1

Випадок використання, з яким я часто зустрічаюсь, - це визначення верхнього / нижнього каталогу після розширення глобальної версії (наприклад, каталог, наповнений розробленими SDK або інструментами побудови). У цій ситуації зазвичай я хочу зберегти це ім'я каталогу у змінній для використання у кількох місцях у сценарії оболонки.

Ця команда зазвичай робить це для мене:

export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)

Відмова від відповідальності: розширення Glob не збирається сортувати ваші папки за semver; вас попередили Це чудово, якщо у вас є Dockerfileлише одна версія каталогу, але ця версія каталогів може змінюватись від зображення до зображення 🤷


Ласкаво просимо до U&L! Це обробляє більшість імен каталогів, але не обробляє імена каталогів з новим рядком. Спробуйте створити подібний каталог mkdir "$(echo one; echo two)"і подивіться, що я маю на увазі.
Flimm

Яка перевага порівняно з іншими альтернативами, особливо з версією, яка використовується tail?
RalfFriedl

Стандарт dirnameмістить лише одне ім'я шляху, тому ви не можете покластися на нього, працюючи над кількома іменами шляхів, якщо ви не знаєте, що ваша конкретна реалізація це підтримує.
Kusalananda

@Flimm Добрий момент; Я думаю, що у більшості розробників виникнуть більші проблеми, якби в їх структурі папок були нові рядки ... Я ніколи з цим не стикався і не сподіваюся на будь-які напівпристойні контейнери та програмне забезпечення, яке я використовую
Андрій Одрі

@RalfFriedl Добре запитання; це по суті фільтрує все, що не є дійсною каталогом (і не буде перераховувати / переходити. і ..)
Ендрю Одрі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.