Скрипт для обмена имен двух файлов

Я новичок в скриптах Bash. Я пытался создать сценарий, который поменяет местами имена двух файлов, переданных ему пользователем.

Вот картина двух версий моего сценария до сих пор

Вот сценарий в текстовом формате с mv:

#! /bin/bash
file1=$file1
file2=$file2

echo "write file name :"
read file1 file2

if [[ $file1 = $file2 ]]
then 
  cp $file1 $file1
  mv $file1 $file2 
fi

if [[ $file2 = $file1 ]]
then   
  mv $file2 $file1
fi

Но мой Вопрос в том, смогу ли я создать сценарий, который позволит пользователю сначала записать 2 имени файла, а затем сценарий поменяет 2 имени файла. ]

7
задан 5 December 2017 в 17:42

2 ответа

Один из возможных способов сделать это

Некоторое время назад я создал функцию специально для этой цели, которая хранится в моем .bashrc, и может быть адаптирована в скрипт. Вы должны воспользоваться позиционными параметрами , чтобы пользователи могли ставить имена файлов в командную строку. Вот моя оригинальная функция:

swap_files() {
    if [ $# -ne 2 ]
    then
        echo "Usage: swap_files file1 file2"
    else
        local TMPFILE=$(mktemp) 
        mv -- "$1" "$TMPFILE"
        mv -- "$2" "$1"
        mv -- "$TMPFILE" "$2"
    fi
}

Вы можете избавиться от объявления swap_files(){, ключевого слова local, и закрыть }, и превратить его в скрипт - просто добавьте #!/bin/bash сверху. Конечно, есть тонны вещей, которые можно улучшить, но на самом базовом уровне это примерно так же просто, как и замена (что, кстати, часто учат на C менять элементы массива, но это всего лишь касательная тема).

#!/bin/bash
if [ $# -ne 2 ]
then
    printf "Usage: swap_files file1 file2\n" > /dev/stderr
else
    TMPFILE=$(mktemp)
    mv -- "$1" "$TMPFILE"
    mv -- "$2" "$1"
    mv -- "$TMPFILE" "$2"
fi

Конечно, не забудьте процитировать позиционные параметры, если имена файлов содержат пробелы. Например:

swap_files 'file 1' 'file 2'

Обратите внимание на использование --, чтобы избежать проблем с именами файлов, которые приводят к - в них. Лучшим способом было бы войти в привычку ссылаться на файлы в текущем рабочем каталоге с помощью ./, особенно если вы используете globstar * ( globstar не имеет отношения к этому вопросу, но стоит упомянуть, если мы говорим об именах файлов с ведущим -). Кроме того, ./ способ более переносимый, так как некоторые версии mv, такие как во FreeBSD не имеют опции --.


Как предложил Тердон в комментариях, мы также можем создать временный файл в родительской папке первого файла, чтобы избежать перемещения файлов по файловым системам.

#!/bin/bash
if [ $# -ne 2 ]
then
    printf "Usage: swap_files file1 file2\n" > /dev/stderr
else
    file1_dir=${1%/*}
    # just in case there were no slashes removed, assume cwd
    if [ "$file1_dir" = "$1" ]; then
        file1_dir="."
    fi
    tmpfile=$(mktemp -p "$file1_dir" )
    mv -- "$1" "$tmpfile"
    mv -- "$2" "$1"
    mv -- "$tmpfile" "$2"
fi

Ваш скрипт и вещи для улучшения

1. Дублирующее присваивание переменной

file1=$file1
file2=$file2

Эта часть присваивает $file1 переменную к ... file1 переменная; с этим есть две проблемы - присваивание переменной само по себе избыточно, а для начала ее не существует, ранее в сценарии не было объявлено об этой переменной.

2. Остерегайтесь разбиения слова с помощью read

Вот что произойдет, если ваш пользователь попытается вставить даже цитируемые элементы в вашу команду read:

$ read file1 file2
'one potato' 'two potato'

$ echo "$file1"
'one

$ echo "$file2"
potato' 'two potato'

В соответствии с поведением оболочки, оболочка разбивает все, что читается на stdin и пытается поместить каждое слово в соответствующие переменные, а если слова превышают количество переменных - она пытается вставить все в последнюю переменную. Я бы рекомендовал читать в каждом файле, по одному.

3. Копирование в себя - это ошибка

Вы делаете

cp $file1 $file1;

Это приведет к ошибке

$ cp input.txt input.txt
cp: 'input.txt' and 'input.txt' are the same file

Возможно, вы хотели сделать

cp "$file1" "$file1".tmp

Или просто воспользоваться командой mktemp, как это сделал я. Также обратите внимание на кавычки переменных для предотвращения разделения слов.


Другие забавные способы сделать это

Знаете ли вы, что вы можете котировать любой файл с перенаправлением, чтобы сделать копию? Поэтому использование mv или cp не единственный способ. Что-то вроде этого:

$ cat ./wallpaper.jpg > wallpaper.jpg.tmp
$ cat ./screenshot.jpg > wallpaper.jpg
$ cat ./wallpaper.jpg.tmp > ./screenshot.jpg
11
ответ дан 5 December 2017 в 17:42

Вы можете использовать расширение параметров для задачи, если получите два имени файлов или прочитаете их в скрипте. в моем примере ниже скрипт использует расширение параметров. И вы можете захотеть использовать временную директорию для вариантов перемещения, потому что если имя файла, используемое в скрипте, уже существует, то этот файл будет бесшумно перезаписан.

#!/bin/bash

# creating a temporary directory to prevent overwrites if the file called 'tmpfile' exists
TMP=$(mktemp -d)

# copying the filenames into variables for later use
file1="$1"
file2="$2"

# swapping the files
# first move and rename file1 to $TMP/tempfile
mv "$1" $TMP/tempfile
# renaming file2 to file1 name
mv "$2" "$file1"
# now renaming and moving the tempfile to file2 
mv $TMP/tempfile "$file2"
# removing the temporary folder if tempfile is not existent anymore
[ -e $TMP/tempfile ] && echo "Error!" || rm -r $TMP

Из Bash-Manual #Shell-Parameters:

Параметр - это сущность, которая хранит значения. Это может быть имя, a номер, или один из специальных символов, перечисленных ниже. Переменной является параметр, обозначенный именем. Переменная имеет значение и ноль или более атрибуты. Атрибуты назначаются с помощью команды declare builtin (см. описание объявления builtin в Bash Builtins).

Параметр устанавливается, если ему присвоено значение. Нулевой строкой является действительное значение. После того, как переменная установлена, она может быть отменена, только если используется команда set builtin.

Переменной может присваиваться оператор вида

name=[value]

И если вы хотите прочитать в именах файлов из интерактивного диалога:

#!/bin/bash

# creating a temporary directory to prevent overwrites if the file called 'tmpfile' exists
TMP=$(mktemp -d)

# reading the filenames from user input into variables for later use
read -rp "Enter first filename: " file1
read -rp "Enter second filename: " file2

# swapping the files
# first move and rename file1 to $TMP/tempfile
mv "$file1" $TMP/tempfile
# renaming file2 to file1 name
mv "$file2" "$file1"
# now renaming and moving the tempfile to file2 
mv $TMP/tempfile "$file2"
# removing the temporary folder if tempfile is not existent anymore
[ -e $TMP/tempfile ] && echo "Error!" || rm -r $TMP

From Bash-Manual #Bash-Builtins:

 прочитайте [-ers] [-a  имя ]. [-d  разграничение ] [-i  text ] [-n  nchars ]
 [-N  nchars ] [-p  prompt ] [-t  тайм-аут ] [-u  fd ] [ имя ...]

Одна строка считывается со стандартного входа или с файлового дескриптора fd. поставляется в качестве аргумента к опции -u, разбивается на слова, как описано выше в [Разбиение на слова][6], и первое слово присваивается первому имени, второе слово - второму имени, и так далее. Если слов больше, чем имен, остальные слова и их промежуточные разделители присваиваются последнему имени. Если из входного потока считывается меньше слов, чем имен, то оставшимся именам присваиваются пустые значения. Символы в значении переменной IFS используются для разделения строки на слова с использованием тех же правил, которые оболочка использует для разделения (описано выше в [Разделение слов][6]). Символ обратного слеша '\' может быть использован для удаления любого специального значения для следующего считанного символа и для продолжения строки. Если имена не указаны, то считываемая строка присваивается переменной REPLY.

read принимает несколько опций. В этом случае две из них являются наиболее релевантными, так как вы хотите задать пользователю вопрос и получить для него информацию. Эти опции:

  • -r → Если эта опция включена, то обратный слеш не действует как экранирующий символ. Обратный слеш считается частью строки. В частности, пара обратная косая черта не может быть использована в качестве продолжения строки.
  • -p -подсказка → Отображать -подсказку, не имеющую продолжения новой строки, перед попыткой прочитать любой ввод. Подсказка отображается только в том случае, если вход поступает от клеммы.

Хотя это и не самая худшая ситуация, чтобы забыть -r, почти всегда нужно включать ее, чтобы \ не выступал в качестве экранирующего символа. -p показывает пользователю приглашение.

6
ответ дан 5 December 2017 в 17:42

Другие вопросы по тегам:

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