Почему эта команда `grep -v` не работает должным образом?

Это можно "подделать".

Сначала немного теории:

Роб Гриффитс в 2007 году опубликовал статью под названием Легко создать много новых папок на Macworld.com, в которой он обсуждал использование команды xargs для чтения списка файлов для создания каталогов с помощью mkdir.

xargs может ссылаться на placeholder ({}) с флагом -I, который содержит значение для каждого аргумента, переданного в xargs. Вот разница между этим флагом и без него:

$ foo.txt bar.txt | xargs echo
$ => foo.txt bar.txt
$ foo.txt bar.txt | xargs -I {} echo {}
$ => foo.txt
$ => bar.txt

xargs также способен выполнять произвольные команды оболочки с флагом sh -c:

foo.txt bar.txt | xargs sh -c 'echo arbitrary command!'

Объединение концепций:

Мы можем объединить эти концепции с mkdir -p вместо mkdir и концепцией в @ldx's answer, чтобы получить следующее:

$ cat files.txt | xargs -I {} sh -c 'f="{}" && mkdir -p -- "${f%/*}" && touch -- "$f"'

Эта команда в основном сопоставляет каждое имя файла в списке файлов, разделенных строками, отрезает часть файла, создает каталоги с помощью mkdir -p и затем touch помещает имя файла в соответствующий каталог.

Вот описание того, что происходит при выполнении вышеприведенной команды:

Скажем, например, мой files.txt выглядит так:

deeply/nested/foo/bar.txt
deeply/nested/baz/fiz.txt
  • cat files.txt выдает deeply/nested/foo/bar.js deeply/nested/baz/fiz. txt
  • deeply/nested/foo/bar.js deeply/nested/baz/fiz.txt передается в xargs
  • поскольку мы использовали -I {}, xargs переводит каждый аргумент в свою команду, так что теперь мы имеем:
    • deeply/nested/foo/bar.txt
    • deeply/nested/baz/fiz.txt
  • затем мы запускаем команду shell, которая использует комбинатор && для группировки 3 команд, которые выполняются последовательно - первая команда сохраняет файл в переменной окружения (которая будет повторно использована при следующем проходе файла), используя местодержатель, который мы зарегистрировали ранее, так что теперь мы имеем:
    • f=deeply/nested/foo/bar.txt
    • f=deeply/nested/baz/fiz.txt
  • теперь у нас есть переменная, которую мы можем передать в mkdir -p, но нам нужно вырезать имя файла. Достаточно просто, используя '${f%/*}':
    • mkdir -p deeply/nested/foo/
    • mkdir -p deeply/nested/baz/
  • и затем мы просто перессылаемся на переменную f целиком, когда touch:
    • touch deeply/nested/foo/bar.txt
    • touch deeply/nested/baz/fiz.txt

12
11.12.2016, 17:27
2 ответа

Закари объяснил источник проблемы.

Хотя вы можете обойти ее с помощью

tty=$(tty)
tty_without_dev=${tty#/dev/}
who | grep -v "$tty_without_dev"

Это было бы неправильно, поскольку, например, если tty - это pts/1, вы в итоге исключите все строки, содержащие pts/10. Некоторые реализации grep имеют опцию -w для поиска слов

who | grep -vw pts/1

не будет соответствовать pts/10, потому что за pts/1 не следует символ, не являющийся словом.

Или вы можете использовать awk для фильтрации по точному значению второго поля, например:

who | awk -v "tty=$tty_without_dev" '$2 != tty'

Если вы хотите сделать это одной командой:

{ who | awk -v "tty=$(tty<&3)" '$2 != substr(tty,6)'; } 3<&0

Оригинальный stdin дублируется на дескриптор файла 3 и восстанавливается для команды tty.

18
27.01.2020, 19:54

Со страницы информации tty.

'tty' печатает имя файла терминала, подключенного к его стандартному входу . Он печатает «not a tty», если стандартный ввод не является терминалом.

Проблема в том, что в вашем примере stdin tty - это канал, а не ваш терминал.

Вы можете видеть из этого примера.

$ tty
/dev/pts/29
$ echo | tty 
not a tty

Чтобы обойти это, вы можете сделать что-нибудь вроде этого.

who | grep -wv "$(ps ax | awk "\$1 == $$ {print \$2}" )"

Есть более быстрый / более эффективный способ, но он требует двух команд.

t=$(tty)
who|grep -wv "${t:5}"
20
27.01.2020, 19:54

Теги

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