【问题标题】:MacOS shell script to move files based on tagMacOS shell脚本基于标签移动文件
【发布时间】:2019-01-06 01:30:39
【问题描述】:

我正在尝试编写一个 shell 脚本,以便我可以根据输入将学校文件从一个目的地移动到另一个目的地。我从画布之类的源下载这些文件,并希望根据我分配的标签将它们从我的下载中移动到我的课程文件夹的路径中,这要归功于我如何保持井井有条。不幸的是,由于我将这些文件存储在我的 OneDrive 学校帐户中,我无法消除一些间距问题,但我相信我已经解决了这些问题。现在脚本如下:

if [ "$1" = "311" ];
then
    course="'/path/to/311/folder/$2'"

elif [ "$1" = "411" ];
then
    course="'/path/to/411/folder/$2'"

elif [ "$1" = "516" ];
then
    course="'/path/to/516/folder/$2'"

elif [ "$1" = "530" ];
then
    course="'/path/to/530/folder/$2'"

elif [ "$1" = "599" ];
then
    course="'/path/to/599/folder/$2'"

fi

files=$(mdfind 'kMDItemUserTags='$1'' -onlyin /Users/user/Downloads)
#declare -a files=$(mdfind 'kMDItemUserTags='$1'' -onlyin /Users/user/Downloads)
#mv $files $course
#echo "mv $files $course"
#echo $course

for file in $files
#for file in "${files[@]}"
do 
    #echo $file
    #echo $course
    mv $file $course
done

其中 $1 是标签 ID 和路径选择的第一部分,$2 是我要将其移动到的星期数文件夹。单引号用于处理文件路径中的间距。我可以很容易地在 python 中做到这一点,但我正在尝试扩展我的能力。每次运行此脚本时,我都会收到以下消息:

usage: mv [-f | -i | -n] [-v] source target
       mv [-f | -i | -n] [-v] source ... directory

我最初尝试一次全部移动它们(根据第一个被注释掉的 mv 命令)并得到这个错误,然后尝试了 for 循环和数组,但每次都得到相同的错误。但是,当我取消注释 for 循环中的 echo 语句并手动尝试通过将路径复制并粘贴到命令行来移动每个语句时,它可以完美运行。我最好的猜测是与变量“文件”的格式有关,因为

echo "mv $files $course"

表示它保存的每个文件之间存在换行符或分隔符。

我敢肯定,自从上周我刚开始尝试学习 shell 脚本以来,我就错过了一些超级简单的东西,但是我在网上找不到任何东西可以帮助我解决这个问题。任何帮助将不胜感激。谢谢

【问题讨论】:

    标签: bash macos shell


    【解决方案1】:

    您可以用一个命令替换 files 变量赋值和 for 循环,使其成为脚本:

    if [ "$1" = "311" ];
    then
        course="'/path/to/311/folder/$2'"
    
    elif [ "$1" = "411" ];
    then
        course="'/path/to/411/folder/$2'"
    
    elif [ "$1" = "516" ];
    then
        course="'/path/to/516/folder/$2'"
    
    elif [ "$1" = "530" ];
    then
        course="'/path/to/530/folder/$2'"
    
    elif [ "$1" = "599" ];
    then
        course="'/path/to/599/folder/$2'"
    
    fi
    
    mv -t $course $(mdfind 'kMDItemUserTags='$1'' -onlyin /Users/user/Downloads | sed ':a;N;$!ba;s/\n/ /g)
    

    sed ':a;N;$!ba;s/\n/ /g 命令只是用空格替换换行符,mv-t 选项只是让mv 将目标作为第一个参数。

    【讨论】:

      【解决方案2】:

      您对引用在 shell 中的工作方式感到相当困惑。第一条规则:引号是围绕数据,而不是数据。例如,您使用:

      course="'/path/to/311/folder/$2'"
      ...
      mv $file $course
      

      当您以这种方式设置 course 时,双引号将被视为 shell 语法(即,它们会更改解析它们之间的内容的方式),但单引号将作为变量值的一部分存储,此后将被视为数据。当您在mv 命令中使用此变量时,它实际上是在寻找一个字面上命名为单引号的目录,并在该目录下找到一个名为“path”的目录,等等。相反,只需为您希望它的解析方式加上适当的引号 此时,然后在使用变量时用双引号括起来(以防止可能不需要的分词和通配符扩展)。像这样:

      course="/path/to/311/folder/$2"
      ...
      mv "$file" "$course"    # This needs more work -- see below
      

      还有,你在哪里:

      mdfind 'kMDItemUserTags='$1'' -onlyin /Users/user/Downloads
      

      这真的没有任何意义。你有一个单引号部分,'kMDItemUserTags=',其中引号根本没有效果(单引号抑制字符具有的所有特殊含义,例如 $ 引入变量替换,但那里没有任何字符特殊含义,所以没有引号的理由),后跟$ 没有双引号,表示其值中有一些特殊字符(空格和通配符)将得到特殊的解析(您可能不想要),然后是一个零长度的单引号字符串'',它解析出来的内容完全没有。您希望将$1 部分放在双引号中;有些人还将字符串的其余部分包含在双引号部分中,这根本没有任何作用。事实上,除了$2 部分(以及参数之间的空格)之外,您可以根据需要引用或不引用。因此,这些中的任何一个都可以等效地工作:

      mdfind kMDItemUserTags="$1" -onlyin /Users/user/Downloads
      mdfind "kMDItemUserTags=$1" -onlyin /Users/user/Downloads
      mdfind "kMDItemUserTags=$1" '-onlyin' '/Users/user/Downloads'
      mdfind 'kMDItemUserTags'="$1" '-'"only"'in' /'Users'/'user'/'Down'loads
      ...etc
      

      好的,下一个问题:将来自mdfind 的输出从一系列字符解析为单独的文件路径。这实际上很棘手。如果您在重新排序的字符串周围加上双引号,它将被视为一个长文件路径,其中恰好包含一些换行符(这是完全合法的,但不是您想要的)。如果您双引号,它将根据空格(不仅是换行符,还包括空格和制表符——空格在 macOS 文件名中很常见)被拆分为单独的文件路径,和 任何看起来像通配符的东西都将被扩展为匹配文件名的列表。这往往会导致混乱。

      解决方案:文件路径中不能出现一个字符,ASCII NULL(字符代码 0)和mdfind -0 将输出以空字符分隔的列表。您不能将结果放入 shell 变量中(它们也不能保存空值),但您可以通过管道将其传递给例如 xargs -0,这将(感谢 -0 选项)解析空值作为分隔符,并根据结果构建命令。有一件有点棘手的事情:您希望xargs 将它获得的文件路径放在参数列表中间的mv,而不是像通常那样放在最后。 -J 选项让你告诉它在哪里添加参数。我还将建议两个安全措施:xargs-p 选项使其在实际执行命令之前询问(至少在您确定它在做正确的事情之前使用它),以及 -n 选项mv,告诉它如果存在命名冲突不要覆盖现有文件。结果是这样的:

      mdfind -0 kMDItemUserTags="$1" -onlyin /Users/user/Downloads | xargs -0 -p -J% mv -n % "$course"
      

      【讨论】:

        【解决方案3】:

        最好考虑一下带有空格的文件名。 但是问题是您 没有mv 命令中引用文件名。请看下面一个简单的例子:

        filename="with space.txt"
          => assign a variable to a filname with a space
        touch "$filename"
          => create a file "with space.txt"
        str="'$filename'"
          => wrap with single quotes (as you do) 
        echo $str
          => yields 'with space.txt' and may look good, which is a pitfall
        mv $str "newname.txt"
          => causes an error
        

        上面的mv 命令会导致错误,因为该命令是用 三个参数为:mv'withspace.txt'newname.txt。很遗憾 单引号的前置是没有意义的。

        请尝试以下方法:

        if [ "$1" = "311" ]; then
            course="/path/to/311/folder/$2"
        elif [ "$1" = "411" ]; then
            course="/path/to/411/folder/$2"
        elif [ "$1" = "516" ]; then
            course="/path/to/516/folder/$2"
        elif [ "$1" = "530" ]; then
            course="/path/to/530/folder/$2"
        elif [ "$1" = "599" ]; then
            course="/path/to/599/folder/$2"
        else
            # illegal value in $1. do some error handling
        fi
        # the lines above may be simplified if /path/to/*folder/ have some regularity
        
        mdfind "kMDItemUserTags=$1" -onlyin /Users/user/Downloads | while read -r file; do
            mv "$file" "$course"
        done
        # the syntax above works as long as the filenames do not contain newline characters
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-03-06
          • 1970-01-01
          • 1970-01-01
          • 2016-01-29
          • 1970-01-01
          • 2013-11-16
          • 2018-11-16
          • 1970-01-01
          相关资源
          最近更新 更多