交换两个文件名的脚本

Dea*_*l69 7 command-line bash scripts swap

我是 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
Run Code Online (Sandbox Code Playgroud)

但我的问题是,如果我可以制作一个脚本,让用户先写下 2 个文件名,然后脚本将交换 2 个文件名

我读过的交换文件名的基础是这个

cp $file1 temporaryfile
mv $file1 $file2
mv $file2 temporyfile
Run Code Online (Sandbox Code Playgroud)

Ser*_*nyy 11

一种可能的方法

不久前,我专门为此目的创建了一个函数,该函数保存在我的.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
}
Run Code Online (Sandbox Code Playgroud)

您可以去掉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
Run Code Online (Sandbox Code Playgroud)

当然,如果文件名包含空格,请记住引用位置参数。像这样:

swap_files 'file 1' 'file 2'
Run Code Online (Sandbox Code Playgroud)

请注意使用--以避免文件名中带有前导的问题-。更好的方法是养成使用 引用当前工作目录中的文件的习惯./,特别是如果您使用的是 globstar *( globstar 在这个问题中不相关,但如果我们谈论的是带有 Leadership 的文件名,则值得一提-)。此外,./办法是更便携,因为有些版本mvFreeBSD上没有--选择。


正如 terdon 在评论中所建议的,我们还可以在第一个文件的父文件夹中创建临时文件,以避免跨文件系统移动文件。

#!/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
Run Code Online (Sandbox Code Playgroud)

你的剧本和需要改进的地方

1. 冗余变量赋值

file1=$file1
file2=$file2
Run Code Online (Sandbox Code Playgroud)

这部分将$file1变量赋值给...file1变量;这有两个问题 - 将变量分配给自身是多余的,而且它一开始并不存在,脚本中没有先前对该变量的声明。

2. 谨防read 分词

如果您的用户尝试将带引号的项目放入您的read命令中,将会发生以下情况:

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

$ echo "$file1"
'one

$ echo "$file2"
potato' 'two potato'
Run Code Online (Sandbox Code Playgroud)

根据 shell 行为,shell 将读取的所有内容拆分stdin并尝试将每个单词放入相应的变量中,如果单词超过变量的数量 - 它会尝试将所有内容推送到最后一个变量中。我建议您阅读每个文件,一次一个。

3.复制到自己是错误的

你正在做的

cp $file1 $file1;
Run Code Online (Sandbox Code Playgroud)

那会产生错误

$ cp input.txt input.txt
cp: 'input.txt' and 'input.txt' are the same file
Run Code Online (Sandbox Code Playgroud)

也许你想做

cp "$file1" "$file1".tmp
Run Code Online (Sandbox Code Playgroud)

或者mktemp像我一样使用命令。还要注意引用变量以防止分词。


其他有趣的方法

你知道你可以用重定向来捕获任何文件来制作副本吗?所以使用mvorcp不是唯一的方法。像这样的东西:

$ cat ./wallpaper.jpg > wallpaper.jpg.tmp
$ cat ./screenshot.jpg > wallpaper.jpg
$ cat ./wallpaper.jpg.tmp > ./screenshot.jpg
Run Code Online (Sandbox Code Playgroud)

  • 你没有保留临时文件,你正在`mv`ing它。此外,为了进一步改进,您可以使用 `dir1="${file1%/*}"; tmpfile="$(mktemp -p "$dir1")"` 以便临时文件始终在与原始文件相同的文件系统上创建,以避免跨文件系统边界运行 `mv`。 (4认同)

Vid*_*uth 6

如果获取两个文件名,您可以为任务使用参数扩展,或者您可以在脚本中读取它们。我下面的示例脚本使用参数扩展。并且您可能希望为您的移动选项使用一个临时目录,因为如果脚本中使用的文件名已经存在,则该文件将被静默覆盖。

#!/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
Run Code Online (Sandbox Code Playgroud)

来自Bash-Manual #Shell-Parameters

参数是存储值的实体。它可以是名称、数字或下面列出的特殊字符之一。变量是由名称表示的参数。一个变量有一个值和零个或多个属性。使用declare builtin 命令分配属性(请参阅Bash Builtins 中declare builtin 的描述)。

如果一个参数已被赋值,则该参数被设置。空字符串是有效值。一旦设置了变量,只能使用 unset 内置命令取消设置。

一个变量可以通过以下形式的语句赋值

名称=[]

如果您想从交互式对话框中读取文件名:

#!/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
Run Code Online (Sandbox Code Playgroud)

来自Bash-Manual #Bash-Builtins

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
    [-N nchars] [-p prompt] [-t timeout] [-u fd] [name …]
Run Code Online (Sandbox Code Playgroud)

一行是从标准输入中读取的,或者从 作为选项参数提供的文件描述符fd 中读取,-u按照上述 [Word Splitting][6] 中的描述拆分为单词,并且第一个单词被分配给 first name,第二个字到第二个名字,依此类推。如果单词多于名称,则剩余的单词及其中间分隔符将分配给 last name。如果从输入流中读取的单词少于名称,则剩余的名称将被分配空值。IFS变量值中的字符用于使用 shell 用于扩展的相同规则将行拆分为单词(在上面的 [Word Splitting][6] 中描述)。反斜杠字符 '\' 可用于删除下一个字符读取和行继续的任何特殊含义。如果未提供名称,则将读取的行分配给变量REPLY

read接受几个选项。在这种情况下,两个最相关,因为您想向用户提出问题并获得他们的输入。这些选项是:

  • -r? 如果给出此选项,反斜杠不会充当转义字符。反斜杠被认为是行的一部分。特别是,反斜杠-换行符对不能用作换行符。
  • -p prompt? 在尝试读取任何输入之前显示prompt,没有尾随换行符。仅当输入来自终端时才显示提示。

虽然这不是最糟糕的情况-r,但您几乎总是希望包含它以防止\充当转义字符。-p向用户显示提示。