Чому awk зупиняється і чекає, якщо ім'я файлу містить = і як це обійти?


Відповіді:


19

Як каже Кріс , аргументи форми variablename=anythingтрактуються як призначення змінних (які виконуються під час обробки аргументів на відміну від (новіших) -v var=value, які виконуються перед BEGINоператорами) замість імен вхідних файлів.

Це може бути корисно в таких речах, як:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Де ви можете вказати інший FS/ RSна файл. Він також часто використовується в:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Яка безпечніша версія:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(яка не працює, якщо file1порожня)

Але це стає на шляху, коли у вас є файли, ім'я яких містить =символи.

Тепер це лише проблема, коли те, що залишилося від першого, =- це дійсне awkім'я змінної.

Те, що являє собою дійсне ім'я змінної в awk, суворіше ніж у sh.

POSIX вимагає, щоб це було щось на зразок:

[_a-zA-Z][_a-zA-Z0-9]*

Тільки символи портативного набору символів. Однак, /usr/xpg4/bin/awkпринаймні, Solaris 11 в цьому плані не відповідає, і дозволяє будь-які алфавітні символи в мові змінних імен, а не лише a-zA-Z.

Отже, аргумент на зразок x+y=fooабо =barабо ./foo=barдосі трактується як ім'я вхідного файлу, а не присвоєння, оскільки те, що залишилося від першого =, не є дійсним ім'ям змінної. Аргумент на кшталт Stéphane=Chazelas.txtможе бути, а може і не, залежно від awkреалізації та місцевості.

Ось чому з awk рекомендується використовувати:

awk '...' ./*.txt

замість

awk '...' *.txt

наприклад, щоб уникнути проблеми, якщо ви не можете гарантувати, що ім'я txtфайлів не міститиме =символів.

Також майте на увазі, що такий аргумент -vfoo=bar.txtможе трактуватися як варіант, якщо ви використовуєте:

awk -f file.awk -vfoo=bar.txt

(Також відноситься і до awk '{code}' -vfoo=bar.txtз awkвід версії BusyBox до 1.28.0, см відповідного повідомлення про помилку ).

Знову ж таки, використання ./*.txtнавколо цього робіт (використання ./префікса також допомагає з файлом, -який інакше замість цього awkозначає як стандартний ввід ).

Це також чому

#! /usr/bin/awk -f

shebangs насправді не працюють. У той час як var=valueті , можна обійти шляхом фіксації на ARGVзначення (додати ./префікс) в BEGINзаяві:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Це не допоможе з варіантами, оскільки ті, які вони бачать, awkа не awkсценарій.

Один з потенційних косметичних проблем із використанням цього ./префікса - це він закінчується FILENAME, але ви завжди можете substr(FILENAME, 3)його знімати, якщо цього не хочете.

Реалізація GNU awkвиправляє всі ці проблеми з її -Eвибором.

Після цього -Egawk очікує лише шлях awkсценарію (де -все ще означає stdin), а потім список тільки шляхів до вхідних файлів (а там навіть -не обробляється спеціально).

Він спеціально розроблений для:

#! /usr/bin/gawk -E

shebangs, де список аргументів завжди є вхідними файлами (зауважте, що ви все ще можете безкоштовно редагувати цей ARGVсписок у BEGINвиписці).

Ви також можете використовувати його як:

gawk -e '...awk code here...' -E /dev/null *.txt

Ми використовуємо -Eпорожній скрипт ( /dev/null) просто для того, щоб переконатися, що *.txtпісля цього завжди трактуються як вхідні файли, навіть якщо вони містять =символи.


Я не бачу, як явний шлях до FILENAME є проблемою. Або скрипт awk є загальним, і в цьому випадку він повинен обробляти всі види шляхів, що закінчуються у FILENAME (включаючи, але не обмежуючись ними ../foo, /path/to/fooта шляхи, що мають інше кодування) - у цьому випадку substr(FILENAME,3)цього буде недостатньо, або це скрипт з одним знімком, де користувач в основному знає, що таке імена файлів - в такому випадку він / він, мабуть, не повинен заважати жодному з них, що містить =будь-яке ;-)
mosvy

2
@mosvy Я не думаю, що це говорить про те, що ./це проблема, але це може бути небажаним за певних умов, наприклад, коли ім'я файлу повинно бути включене у висновок, у цьому випадку ./має бути зайвим і непотрібним, тож ви Мені потрібно якось позбутися цього. Ось хоча б один приклад . Що стосується того, щоб користувач знав, що таке імена файлів - добре, в цьому випадку ми також знаємо, що таке ім'я файлу, але =все-таки перешкоджає правильній обробці. Так що провідні можуть -заважати.
Сергій Колодяжний

@mosvy, так, ідея полягає в тому, що ви хочете використовувати ./префікс, щоб обійти цю awk(неправильну) функцію, але потім ви отримаєте той, що ./на виході, який ви можете зняти. Подивіться, як перевірити, чи містить перший рядок файлу певний рядок? як приклад.
Стефан Шазелас

Інтерпретувати аргумент як файл - це не лише локальний (відносно цього каталогу), ./а й глобальний (абсолютний шлях) /.
Ісаак

21

У більшості версій awk аргументами після виконання програми є або:

  1. Файл
  2. Призначення форми x=y

Оскільки ваше ім'я інтерпретується як випадок №2, awk все ще чекає, що прочитати на stdin (оскільки він не сприймає, що було передано якесь ім’я файлу).

Портативно ця поведінка задокументована в POSIX :

Будь-який із наступних типів аргументів може бути змішаний:

  • file: ім'я файлу, який містить вхід для зчитування, який співпадає з набором шаблонів у програмі. Якщо жодні операнди файлів не вказані, або якщо файловий операнд "-", використовується стандартний ввід.
  • призначення: операнд, який починається з символу підкреслення або алфавіту з переносного набору символів (див. таблицю в томі базових визначень IEEE Std 1003.1-2001, розділ 6.1, Портативний набір символів), після чого послідовність підкреслення, цифри, а алфавіти з переносного набору символів, а потім символ "=", повинні вказувати змінне призначення, а не ім'я шляху.

Як таке, портативно, у вас є кілька варіантів (№1, мабуть, найменш нав'язливий):

  1. Використовуйте awk ... ./my=file, який переходить до цього, оскільки .це не "підкреслювальний або алфавітний символ з набору переносних символів".
  2. Помістіть файл на stdin, використовуючи awk ... < my=file. Однак це не добре працює з кількома файлами.
  3. Тимчасово зробіть жорстке посилання на файл і скористайтеся цим. Можна зробити щось на кшталт ln my=file my_file, а потім використовувати my_fileяк звичайне. Копіювання не буде виконуватися, і обидва файли будуть підкріплені тими ж даними та метаданими inode. Після його використання безпечно видалити створене посилання, оскільки кількість посилань на inode все ще буде більше 0.

6
Не ./my=file працює? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Це повинно бути портативним, оскільки ./myце неправдиве ім'я змінної, тому його не слід аналізувати таким чином.
Стівен Харріс

2
Як зазначено в тексті POSIX, проблема виникає лише тоді, коли першому =передує підкреслювальний або алфавітний символ із портативного набору символів (див. Таблицю в томі базових визначень IEEE Std 1003.1-2001, розділ 6.1, Портативний набір символів), з наступною послідовністю підкреслення, цифр та алфавітів з набору переносних символів . тому шлях до файлу , як ++foo=bar.txtі =fooчи ./foo=barвсе в порядку , як .і +не є [_a-zA-Z].
Стефан Шазелас

1
@SergiyKolodyazhnyy awk є зовнішньою оболонкою, тому не має значення, яким ви користуєтесь. ./my=fileбуде передано дослівно.
Кріс Даун

1
@SergiyKolodyazhnyy, те ж саме для awk '{print $1,$2}' /etc/passwd. Справа в тому, що відкриття файлу оболонки на відміну від awk не має жодних значень щодо того, чи робить його шукаючим чи ні. Насправді, у цьому випадку awk '{exit}' < /etc/passwd, ви б очікували, awkщо звернетесь до кінця першого запису, exitщоб переконатися, що він залишив позицію в межах stdin там. POSIX вимагає цього. /usr/xpg4/bin/awkробить це на Solaris, але ні, gawkні mawkздається, це робити в GNU / Linux.
Стефан Шазелас

3
@mosvy, дивіться розділ ВХІДНІ ФАЙЛИ на веб- сайті pubs.opengroup.org/onlinepubs/9699919799/utilities/… Це корисно у ряді моделей використання, які мають сенс лише у звичайних файлах, наприклад, коли ви хочете врізати файл або записати в нього дані за адресою позиція, визначена awkтаким чином.
Стефан Шазелас

3

Щоб процитувати документацію gawk (додано наголос):

Будь-які додаткові аргументи в командному рядку зазвичай трактуються як вхідні файли, які обробляються у визначеному порядку. Однак аргумент, що має форму var = value, присвоює значення значенню змінної var - він взагалі не вказує файл.

Чому команда зупиняється і чекає? Оскільки у формі awk 'processing_script_here' my=file.txt немає файлу, визначеного вищевказаним визначенням - my=file.txtінтерпретується як присвоєння змінної, і якщо немає визначеного файлу awk, читатиме stdin (також видно, з straceякого видно, що awk у такій команді чекає на read(0,'...)syscall.

Це також описано в специфікації AWK POSIX см операнди розділ і Призначення частина цього)

Змінне призначення очевидно, awk '{print foo}' foo=bar /etc/passwdщо значення fooдрукується для кожного рядка в / etc / passwd. Вказання ./foo=barабо повний шлях проте працює.

Зверніть увагу , що працює straceна awk '1' foo=bar, а також перевірки з cat foo=barпоказує , що це AWK-специфічна проблема, і execve робить шоу імені файлу в якості аргументу передається, тому снаряди не мають нічого спільного зі змінним окр завдань в цьому випадку.

Крім того, зауважте, що awk '...script...' foo=barце не спричинить створення змінних оточення оболонками, оскільки призначення змінних оточуючих середовищ має передувати команді, що набуває чинності. Див. POSIX Shell Grammar Rules , пункт № 7. Додатково це можна перевірити за допомогоюawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.