В чем разница между двойным -цитированием и отсутствием двойного -цитирования массива в Bash?

В библиотеке есть версии символов. Если новый код сталкивается с одним из символов версии, и эта версия новее, чем что-либо в вашей libc, вы не сможете запустить

Могут ли они строить статику?

4
23.04.2020, 19:09
3 ответа

Часть проблемы, судя по вашему фрагменту кода, может заключаться в том, что вы анализируете вывод ls. Это опасно и чревато множеством проблем, и этого лучше избегать.

Вместо

declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do

намного проще (и безопаснее! от )до:

for file in *; do

В этом случае:

for file in *; do
  sha256sum "${file}" | head -c 64
done

readarrayпо мере того, как вы его вызываете, он также помогает сохранять переданные ему литеральные данные, включая символы новой строки. Поэтому, когда вы повторяете значение в кавычках, новая строка сохраняется. когда вы не заключаете его в кавычки, оболочка использует его как пробел между маркерами, чтобы игнорировать его. Вот почему sha256sumне работает. Если у вас есть файл с именем foo, readarrayпередает значение foo\n, которое не соответствует файлу. Раскавычивание этого «исправляет» проблему, случайно выбрасывая часть значения вашей переменной.

3
19.03.2021, 02:26

Нет абсолютно никаких проблем с цитированием расширения массива.

И, конечно же, нет никаких проблем с тем, чтобы не цитировать это, если вы знаете и принимаете последствия. Любое расширение без кавычек -подлежит разделению и подстановке. И в вашем коде ${filelist[…]}подлежит удалению символа IFS (и разделению, если строка содержит какие-либо <space>, <tab>или<newline>).

Это то, что с расширением un -в кавычках сделать, удалить замыкающий <newline>.

Что создает эту проблему, так это то, что вы используете readarrayбез удаления конечного разделителя из каждого элемента массива.
При этом в конце остается <newline>, что отражается в сообщении об ошибке.

Вы могли бы использовать:

readarray -t filelist < <(ls -A)

Опция -tудалит все символы новой строки в конце каждого имени файла.

-t Remove a trailing delim (default newline) from each line read.


Но в вашем коде есть некоторые дополнительные проблемы.

  • Нет необходимости объявлять или очищать массив filelist. По умолчанию это делается с помощью readarray. Это необходимо сделать в некоторых других случаях.

  • Нет необходимости разбирать вывод ls, на самом деле это плохая идея. Самый простой способ получить список файлов в массиве — просто:

    filelist=(./* )
    

    И, чтобы сделать это еще лучше, было бы неплохо избегать каталогов:

    for file in./*; do
      [[ -f $file ]] && filelist+=( "$file" )
    done
    
  • В цикле следует использовать значение переменной $file:

    for file in "${filelist[@]}"; do
      sha256sum "$file" | head -c 64
    done
    

    Если только вы не используете for file in "${!filelist[@]}"; do, в котором будут перечислены ключи массива.

  • Весь список можно обработать всего одним вызовом sha256sum:

    sha256sum "${filelist[@]}" | cut -c -64
    

Улучшенный скрипт:

filelist=()              # declare filelist as an array and empty it.
for file in./*; do
    if [[ -f $file ]]; then
        filelist+=( "$file" )
    fi
done
declare -r filelist      # declare filelist as readonly.
sha256sum "${filelist[@]}" | cut -c -64
7
19.03.2021, 02:26

I'm not worried about word splitting in this case

На самом деле, вы полагаетесь на него для удаления завершающего символа новой строки из элементов массива!

Bashreadarray(mapfile)оставляет разделители по умолчанию. На справочной странице или в справке по командной строке это явно не указано, но есть возможность удалить разделитель, поэтому по умолчанию он не удаляется :

.
-t     Remove a trailing delim (default newline) from each line read.

Итак, фактическая строка в массиве — это file1[newline].

Без кавычек при разделении слов удаляются конечные пробелы, фиксируя новую строку. Но если бы у вас были имена файлов с пробелами, разделение слов, как обычно, испортило бы их. Двойное цитирование массива предотвращает это. Чтобы ответить на ваш первый вопрос, наилучшей практикой является двойная -кавычка , здесь у нас просто нежелательная лишняя новая строка.

(Заключение массива в двойные кавычки или $@— это немного сбивающий с толку исключительный случай, когда строка в двойных -кавычках приводит к множеству слов, по одному для каждого элемента массива.)

У вас также есть ${filelist[$file]}в командной строке sha256sum. Это не сработает, fileуже содержит значение, полученное из массива, а не индекс.

В качестве минимальной модификации это может сработать:

declare -a filelist
readarray -t filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
    sha256sum "$file" | head -c 64
done

(Я также не думаю, что явное declareдействительно необходимо.)


Вышеупомянутая проблема не имеет ничего общего с lsкак таковой. Вы получите ту же проблему, если у вас есть имена файлов, хранящиеся в файле, по одному в строке, и вы используете readarray/ mapfileдля их чтения без использования опции -t. (Или, если вы читаете вывод find, но в этом случае вы можете использовать вместо этого find -exec.)

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

В Bash вы могли вместо этого заполнить массив глобусом:

shopt -s dotglob
filelist=(*)
for file in *; do...

Или просто запустите цикл на глобусе без сохранения в массив:

shopt -s dotglob
for file in *; do...

Обратите внимание, что вам нужно shopt -s dotglob, чтобы *соответствовало точечным файлам , и это зависит от оболочки -.

5
19.03.2021, 02:26

Теги

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