Я новичок в скриптах 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 имени файла. ]
Некоторое время назад я создал функцию специально для этой цели, которая хранится в моем .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
file1=$file1
file2=$file2
Эта часть присваивает $file1
переменную к ... file1
переменная; с этим есть две проблемы - присваивание переменной само по себе избыточно, а для начала ее не существует, ранее в сценарии не было объявлено об этой переменной.
Вот что произойдет, если ваш пользователь попытается вставить даже цитируемые элементы в вашу команду read
:
$ read file1 file2
'one potato' 'two potato'
$ echo "$file1"
'one
$ echo "$file2"
potato' 'two potato'
В соответствии с поведением оболочки, оболочка разбивает все, что читается на stdin
и пытается поместить каждое слово в соответствующие переменные, а если слова превышают количество переменных - она пытается вставить все в последнюю переменную. Я бы рекомендовал читать в каждом файле, по одному.
Вы делаете
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
Вы можете использовать расширение параметров для задачи, если получите два имени файлов или прочитаете их в скрипте. в моем примере ниже скрипт использует расширение параметров. И вы можете захотеть использовать временную директорию для вариантов перемещения, потому что если имя файла, используемое в скрипте, уже существует, то этот файл будет бесшумно перезаписан.
#!/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
показывает пользователю приглашение.