Как правильно использовать символ новой строки в качестве разделителя для сохранения ввода в массив - PullRequest
0 голосов
/ 26 апреля 2020

Я работаю над сценарием bash, который должен создавать резервные копии папок, которые могут указать пользователи. Сценарий просто для удовольствия и для входа в bash. Я не буду использовать его для целей реального резервного копирования.

Идея состоит в том, что пользователи могут вводить определенные c папки, которые они хотят обновить. После каждой записи они нажимают ENTER, а затем вводят другой каталог в консоль. После того, как они закончат, они нажмут CTRL-D. Затем входные данные будут сохранены в массив USER_DIR. Затем этот массив будет проверен, чтобы увидеть, существуют ли каталоги. Если они существуют, запись останется прежней. Если они не существуют, запись будет перезаписана по умолчанию /home/$USER.

Я нашел интересную идею в AskUbuntu (я использую Ubuntu):

while read line
do
    my_array=("${my_array[@]}" $line)
done
echo ${my_array[@]}

Я использовал это и попытался ввести несколько каталогов, но это работало только в том случае, если вход был точно одним каталогом. Каждый раз, когда я использовал несколько каталогов в качестве входных данных, по умолчанию оно равнялось /home/$USER. Я поместил echo ${my_array[@]} в разные позиции в сценарии, и это выглядело так, как будто проблема была с разделителем, поскольку входные данные, казалось бы, были соединены с пробелами.

Я пытался найти путь к l oop через массив и изменить разделитель после сохранения ввода. Я нашел интересную идею о StackOverflow :

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
names=($names) # split to array $names
IFS=$SAVEIFS   # Restore IFS

Я попробовал это, но он также принимает только единичный ввод. Мой сценарий выглядит следующим образом:

echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
while read line
do
    USER_DIR=("${USER_DIR[@]}" "$line")
done

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

# check if user input in array is valid (directory exists)
for ((i=0; i<${#USER_DIR[@]}; i++))
do
    if [[ -d "${USER_DIR[$i]}" ]]; then
        # directory exists
        USER_DIR=("${USER_DIR[@]}")
        echo "${USER_DIR[@]}" # for debugging
    else
        # directory does not exist
        USER_DIR=("/home/$USER")
        echo "${USER_DIR[@]}" # for debugging
    fi
done

# show content of array
echo "Backups of the following directories will be done:"
for i in "${USER_DIR[@]}"; do echo "$i"; done

Любая помощь будет принята с благодарностью. Спасибо за ваше время.

Ответы [ 2 ]

1 голос
/ 26 апреля 2020

Здесь есть пара серьезных проблем, и у меня есть еще несколько небольших рекомендаций. Первый (вероятно) второстепенный: ваш сценарий не начинается со строки shebang, чтобы сообщить системе, на каком языке сценариев он написан. Вы используете bash функции (а не только базовую c оболочку POSIX), поэтому вам нужно начните с shebang, который говорит, что нужно запускать bash, а не с какой-нибудь обобщенной c оболочкой Таким образом, первая строка должна быть либо #!/bin/bash или #!/usr/bin/env bash.

Далее, давайте посмотрим на while read l oop:

while read line
do
    USER_DIR=("${USER_DIR[@]}" "$line")
done

Это работает правильно как есть. Он заполняет массив вводом пользователя, по одному элементу на запись. Похоже, вы использовали echo ${USER_DIR[@]}, чтобы проверить полученный массив, и запутались, думая, что что-то не так; но l oop работает нормально, это echo ${USER_DIR[@]}, что вводит в заблуждение. Как я уже сказал в комментарии, вместо этого используйте declare -p USER_DIR, а bash напечатает инструкцию, которая (если бы вы ее запустили) воссоздала текущее содержимое массива. Это оказывается хорошим, как правило, однозначным способом отображения содержимого массива.

У меня есть несколько небольших рекомендаций: используйте имена переменных в нижнем или смешанном регистре (например, user_dir, userDir, или что-то в этом роде) вместо имен всех заглавных букв. Существует множество названий всех заглавных букв со специальным значением, и если вы используете названные заглавные буквы, легко использовать одно из них и вызвать хаос. Кроме того, я бы инициализировал массив пустым до l oop с user_dir=(), просто чтобы убедиться, что он начинается пустым. И, как сказал Jetchisel в комментарии, user_dir+=("$line") будет более простым способом добавления новых элементов в массив. Только не оставляйте скобки, иначе он будет добавлен к первому элементу вместо добавления нового элемента.

Хорошо, следующий раздел:

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

Это выглядит как попытка решить проблему, которая не существует, которая в конечном итоге создает новую проблему в процессе. Как я уже сказал, USER_DIR уже существует как (правильно заполненный) массив, поэтому $USER_DIR (без [@] или чего-либо еще) просто получает свой первый элемент. Это означает, что USER_DIR=($USER_DIR) получает только первый элемент, пытается разбить его на новые строки (потому что для этого установлено значение * 1029), но их нет, поэтому ничего не происходит. Затем он устанавливает USER_DIR в массив, состоящий из только этого первого элемента .

По сути, он удаляет все, кроме первого элемента из массива. Просто избавьтесь от всего этого раздела.

Хорошо, следующий:

for ((i=0; i<${#USER_DIR[@]}; i++))

Это будет работать в bash для массива с индексацией по умолчанию (что у вас есть), но мое личное предпочтение - использовать это вместо:

for i in "${!USER_DIR[@]}"

Видите, что там !? Это позволяет получить список значений индекса в массиве. Работает в bash, работает в zsh (который использует другое соглашение о нумерации), и мне не нужно пытаться вспомнить, какой из них использует какое соглашение. Это даже работает с ассоциативными массивами! Но опять же, это не имеет большого значения, просто мои предпочтения.

Теперь внутри l oop:

    if [[ -d "${USER_DIR[$i]}" ]]; then
        # directory exists
        USER_DIR=("${USER_DIR[@]}")

Это расширяет содержимое массива (правильно указано и все таким образом, это не будет испорчено), и назначает это прямо обратно в массив. Это абсолютно ничего не делает! Имейте в виду, что здесь нет ничего правильного, но есть гораздо более простые способы ничего не делать. Я бы полностью удалил его и просто использовал if [[ ! -d "${USER_DIR[$i]}" ]]; then, а затем часть «что делать, если его не существует». Кстати, о:

    else
        # directory does not exist
        USER_DIR=("/home/$USER")

Это вторая серьезная проблема. Он заменяет весь массив одной записью, указывающей на ваш домашний каталог. Вы хотите заменить только текущей записью и оставить остальную часть массива в покое, поэтому используйте USER_DIR[$i]="/home/$USER". Обратите внимание, что вокруг ссылок на массивы нет ${ } - это для случаев, когда вы получаете значение из массива, а не когда вы устанавливаете единицу.

Хорошо, еще одна рекомендация: вместо того, чтобы читать все значения в массив и затем возвращаться к ним, чтобы исправить те, которые не существуют, почему бы просто не выполнить тест на существование, когда вы читаете пути к каталогам, а затем просто добавить правильная вещь для массива в первую очередь? Вы даже можете оставлять отзывы пользователей, когда они вводят имена, например:

#!/bin/bash

echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
user_dir=()
while read line
do
    if [[ -d "$line" ]]; then
        user_dir+=("$line")
    else
        echo "$line isn't an existing directory; we'll back up /home/$USER instead"
        user_dir+=("/home/$USER")
    fi
done

# show content of array
echo "Backups of the following directories will be done:"
for i in "${user_dir[@]}"; do echo "$i"; done
1 голос
/ 26 апреля 2020

Вам не нужен этот фрагмент кода.

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

По умолчанию команда чтения использует \n в качестве разделителя ввода.

Демо:

$./test.ksh 
Please enter absolute path of directory for backup. Default is /home/renegade
You can choose multiple directories. Press CTRL-D to start backup.
abc
123
def
Backups of the following directories will be done:
abc
123
def
$cat test.ksh 
echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
while read line
do
    USER_DIR=( "${USER_DIR[@]}" "$line" )
done

#SAVEIFS=$IFS   # Save current IFS
#IFS=$'\n'      # Change IFS to new line
#USER_DIR=($USER_DIR) # split to array $names
#IFS=$SAVEIFS   # Restore IFS

# show content of array
echo "Backups of the following directories will be done:"
for i in "${USER_DIR[@]}"; do echo "$i"; done
$
...