Как заменить звездочку экранированной звездочкой в bash?

#bash

#bash

Вопрос:

У меня есть школьное задание, в котором я должен написать скрипт, который найдет все файлы в указанном каталоге (и все его подкаталоги), которые содержат их имена внутри себя. Все это достаточно просто, но я столкнулся с проблемой. Есть файл, который называется просто '*' . Я использую find для поиска всех файлов в указанном каталоге, и когда он находит этот файл, он все перепутывает и печатает все файлы три раза. Итак, я подумал, что могу решить проблему, экранировав * символ в find выходных данных, но я не могу этого сделать. Допустим find , выходные tests/* данные, и мне нужно их изменить tests/* . Вот что я пробовал:

sed 's/*/\*/g'

awk '{gsub("*", \*); print} '

Ни один из них не сработал. Вывод по-прежнему tests/* . Я перепробовал все возможные комбинации обратной косой черты и звездочек, даже пытался как-то использовать одинарные кавычки, но я просто не могу заставить это работать. У кого-нибудь есть идеи, как это сделать?

РЕДАКТИРОВАТЬ: для дальнейшего уточнения, вот как выглядит моя ситуация. У меня есть каталог, tests который содержит файлы '*' , test и z7.sh . Я хочу найти все файлы в этом каталоге и сохранить их в массиве files . Итак, я передал каталог в качестве аргумента скрипту, а затем сделал:

 IFS=

Это выводит следующее:  tests/* tests/test tests/z7.sh tests/test tests/z7.sh tests/test tests/z7.sh  это явно неправильно, мне нужно, чтобы вывод был tests/* tests/test tests/z7.sh  . Я думал, что решением будет экранировать звездочку, но я даже этого не могу сделать.


Комментарии:

1. Внедрение экранирующих символов и / или кавычек не работает в большинстве ситуаций, потому что оболочка анализирует кавычки и экранирует перед расширением переменных и заменой команд (а не после). См . BashFAQ # 20: Как я могу найти и безопасно обрабатывать имена файлов, содержащие символы новой строки, пробелы или и то, и другое? (те же методы защищают от подстановочных знаков). И заключите все замены переменных и команд в двойные кавычки, чтобы они не подвергались разделению слов или расширению подстановочных знаков.

2. @oguzismail Я буду использовать grep для каждого из элементов массива (то есть для каждого файла) и выясню, какой из файлов содержит его имя внутри себя. Поэтому, если файл с именем Mike содержит предложение "Привет, меня зовут Майк", grep найдет его. Эту часть я уже сделал, и она работает, мне просто нужно исправить звездочку, потому что из-за этого я не получаю правильный список файлов.

3. Это все равно не сработало, теперь вывод tests/* tests/test tests/z7.sh tests/test tests/z7.sh

Ответ №1:

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

Чтобы избежать этого, вы можете добавлять элементы один за другим в массив:

 # IFS='' prevents splitting filenames with spaces
while IFS='' read -r line; do
  files =("$line")
done < <(find "$1" -type f)
 

Или, с помощью bash 4.x , вы можете использовать mapfile/ readarray (они являются синонимами, но readarray может быть более выразительным):

 mapfile -t files < <(find "$1" -type f)
 

Но если вам действительно нужно, вы можете изменить IFS bash и отключить расширение глобуса для этой команды:

 # back the current IFS so we can restore it later
oldifs=$IFS
# don't split filenames with spaces
IFS=''
# disable glob expansion
set noglob
files=($(find "$1" -type f))
# restore previous state
IFS=$oldifs
set glob
 

У Shellcheck (инструмент статического анализа для Bash) есть действительно хорошая страница об этой проблеме: SC2207. Я бы рекомендовал иметь для этого линтер в вашем предпочтительном редакторе, чтобы помочь с таким неожиданным поведением.

Ответ №2:

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

 $ echo 'tests/*' | sed 's/*/\*/g'
tests/*
 

И в awk, если вы не заключаете регулярное выражение между косыми чертами, оно анализируется как строка, а неэкранированные обратные косые черты используются перед преобразованием в ERE.

 $ echo 'tests/*' | awk '{gsub(/*/, "\*")} 1'
tests/*
 

Но я бы не стал беспокоиться об этом, поскольку есть лучшие способы выполнить описанную вами задачу. Например:

 find "$1" -type f -exec sh -c '
for f; do
  if grep -qF -- "${f##*/}" "$f"; then
    printf '''%sn''' "$f"
  fi
done' sh {}  
 

n'
files=($(find "$1" -type f))
echo ${files[@]}
Это выводит следующее: tests/* tests/test tests/z7.sh tests/test tests/z7.sh tests/test tests/z7.sh это явно неправильно, мне нужно, чтобы вывод был tests/* tests/test tests/z7.sh . Я думал, что решением будет экранировать звездочку, но я даже этого не могу сделать.

Комментарии:

1. Внедрение экранирующих символов и / или кавычек не работает в большинстве ситуаций, потому что оболочка анализирует кавычки и экранирует перед расширением переменных и заменой команд (а не после). См . BashFAQ # 20: Как я могу найти и безопасно обрабатывать имена файлов, содержащие символы новой строки, пробелы или и то, и другое? (те же методы защищают от подстановочных знаков). И заключите все замены переменных и команд в двойные кавычки, чтобы они не подвергались разделению слов или расширению подстановочных знаков.

2. @oguzismail Я буду использовать grep для каждого из элементов массива (то есть для каждого файла) и выясню, какой из файлов содержит его имя внутри себя. Поэтому, если файл с именем Mike содержит предложение «Привет, меня зовут Майк», grep найдет его. Эту часть я уже сделал, и она работает, мне просто нужно исправить звездочку, потому что из-за этого я не получаю правильный список файлов.

3. Это все равно не сработало, теперь вывод tests/* tests/test tests/z7.sh tests/test tests/z7.sh

Ответ №1:

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

Чтобы избежать этого, вы можете добавлять элементы один за другим в массив:


Или, с помощью bash 4.x , вы можете использовать mapfile/ readarray (они являются синонимами, но readarray может быть более выразительным):


Но если вам действительно нужно, вы можете изменить IFS bash и отключить расширение глобуса для этой команды:


У Shellcheck (инструмент статического анализа для Bash) есть действительно хорошая страница об этой проблеме: SC2207. Я бы рекомендовал иметь для этого линтер в вашем предпочтительном редакторе, чтобы помочь с таким неожиданным поведением.

Ответ №2:

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


И в awk, если вы не заключаете регулярное выражение между косыми чертами, оно анализируется как строка, а неэкранированные обратные косые черты используются перед преобразованием в ERE.


Но я бы не стал беспокоиться об этом, поскольку есть лучшие способы выполнить описанную вами задачу. Например: