web-dev-qa-db-ja.com

最初の特殊文字を使用したファイル名の処理(例:♫)

最近、名前が文字「the」で始まるファイルを見つけました。このファイルをコピーしてffmpegにフィードし、ターミナルで他のさまざまな方法で参照したかったのです。私は通常、奇妙なファイル名を自動補完しますが、最初の文字を入力することさえできないため、これは失敗します。

コピーアンドペースト操作を実行するためにマウスに切り替えたくありません。考えられるシナリオについて、一連のコードを記憶したくない。私の特別な解決策は、vimに切り替えて貼り付けた!lsで問題の文字をコピーし、終了してターミナルに貼り付けます。これはうまくいきましたが、かなり恐ろしいです。

そのようなシナリオに対処する簡単な方法はありますか?

注:変更する場合は、魚のシェルを使用しています。

30
ZirconCode

ファイル名の最初の文字が印刷可能であるが、英数字も空白もない場合は、[[:punct:]]グロブ演算子を使用できます。

$ ls *.txt
f1.txt  f2.txt  ♫abc.txt
$ ls [[:punct:]]*.txt
♫abc.txt
35
jimmij

私に発生する最も簡単なのはls [^a-zA-Z0-9]*そしてそれは私にとってはトリックですが、terdonの答えはextglobシェルオプションまたはシェルに依存しないアプローチに注意を向けるのに優れています。

6
user86880

lsには、印刷できない文字を処理するためのいくつかのスイッチ(--quote-name、-escape、-literalなど)がありますが、この場合、文字は「印刷可能」であるが「入力可能」ではないようです(少なくとも私のキーボードでは! )、これらのスイッチはどれも役に立たないようです。

したがって、名前に文字が含まれているファイルを取り除くための一般的な「ブルートフォース」アプローチとして、次のようにすることができます。

$ /bin/ls -1A|cat -n  # list all files (except . and ..), 1 per line, add line numbers
     1  ♫
     2  f1.txt
     3  f2.txt

問題のあるファイルを含む行を見つけます。おそらく1行目ですが、5行目だとしましょう。 5行目を印刷し、16進数でエンコードします。

$ /bin/ls -1A|sed -n 5p|xxd -g 1
0000000: e2 99 ab 0a                                      ....

0a(改行)文字を無視してエスケープ文字列を作成し、echoの-eオプションを使用してエスケープを変換します。

$ echo -e '\xe2\x99\xab'
♫

これで、次のようにコピー/移動/削除できます。

$ cp -vi $(echo -e '\xe2\x99\xab') better_name
‘♫’ -> ‘better_name’

また、シェルスクリプトの使用に限定されていない場合は、Pythonで次のように実行できます。

$ python
>>> import os
>>> os.listdir('.')
[ ..., '\xe2\x99\xab', ... ]
>>> print '\xe2\x99\xab'
♫
>>> import shutil
>>> shutil.copy('\xe2\x99\xab', 'better_name')

このアプローチを使用すると、多くのファイルを処理できます。正しいファイルを選択し、壊さずにファイル名を変更するためのロジックを記述するだけです。

for f in os.listdir('.'):
  if not f.isalnum():
    newname = generate_newname(f)
    if not os.path.exists(newname):
      shutil.copy(f, newname)
    else:
      print newname, 'already exists!'
6

同様のアプローチは、「通常の」文字で始まらないすべてのファイルをリストすることです。 bashではこれを行うことができます

$ shopt -s extglob
$ ls !([[:alpha:]]*)

ただし、これはfishでは使用できないようなので、代わりにfindを使用できます。

$ find . -type f -not -name '[[:alpha:]]*'
5
terdon

シンボリックリンクの名前を変更する

ファイル名の最初の文字またはその他の場所に特殊文字を含むファイル名を処理する1つの方法は、名前をより簡単な名前に変更することです

これは、元のファイル名を保持する必要がある場合でも使用できます:ファイル名のコピーの名前を変更します。
これは、ファイルをコピーすることで実行できますが、ファイルへのsymlinksまたはhardlinksを作成し、これらの名前を変更することでも実行できます。 cpは、オプション-s(ハードリンクの場合は-l)を使用して、コピーの代わりにシンボリックリンクを作成します。

「detox」を使用して名前をクリーンアップします

クリーンなファイル名に名前を変更するには、 detox を使用できます。 detoxrc ファイルで定義されているさまざまなルールに従って、ファイル名をクリーンアップするためにファイル名を変更します。デフォルトでは、UTF8文字は削除されるだけです。オプション-s utf_8-onlyを使用すると、_に置き換えられます。

$ touch '♫ 漢字カ' ♫foo
$ ls -1
♫foo
♫ 漢字カ
$ detox -s utf_8-only * 
$ ls -1                
_ ___
_foo


シンボリックリンクの「デトックス」

上記のようなシンボリックリンクでの作業と組み合わせると:

$ mkdir orig
$ cd orig 
$ touch '♫ 漢字カ' ♫foo
$ cd ..
$ mkdir clean
$ cd clean 
$ cp -s ../orig/* .
$ ll               
lrwxrwxrwx 1 14 Oct  8 05:52 ♫foo -> ../orig/♫foo
lrwxrwxrwx 1 21 Oct  8 05:52 ♫\ 漢字カ -> ../orig/♫\ 漢字カ
$ ls -1
♫foo
♫ 漢字カ
$ detox --special -s utf_8-only *
$ ll                                
lrwxrwxrwx 1 21 Oct  8 05:52 _\ ___ -> ../orig/♫\ 漢字カ
lrwxrwxrwx 1 14 Oct  8 05:52 _foo -> ../orig/♫foo
4
Volker Siegel

Fishブラケットワイルドカード ¹をサポートしていません。

function find_special_filename
    find ! -path './.*' -name '[^-.a-zA-Z0-9_]*' $argv
end

このコマンドは非表示のディレクトリを検索せず、lettersdigits._-で始まっていないファイル名を表示します(cf findのドキュメント)。

注:$argvは、関数を含む特殊配列変数(魚シェル)ですしたがって、引数は、基になるコマンドが任意の式(例 alias )を受け取る可能性があります。

find_special_filename -exec mv '{}' misc/ \;

¹ 実際、Fishはブラケット展開(配列変数展開)をサポートしていますが、Bashは別の用語(パラメーターとファイル名の展開)を使用しています。

2
Fólkvangr

私はfishを使用していませんが、 documentation は、16進文字コードの前に\u(16ビット文字の場合)または\U(32ビット文字の場合)。 のコードは491ebだと思うので、次のようにできます。

mv \U000491ebabc.mp3 abc.mp3

♫abc.mp3の名前を変更します。

先行ゼロが必要であることに注意してください。そうでない場合、最後のabcは16進数として扱われ、文字コードの一部になります。 32ビット文字の場合、8桁を入力する必要があります。

2
Barmar

あなたが質問したときにそれが2014年にすでに当てはまったかどうかはわかりませんが、fishの現在のバージョン(2019現在)では、 Tab 2回。矢印キーを使用して、ファイル名の一部を入力せずに目的のファイルを視覚的に選択できるzshスタイルの選択を取得します。

2

zshを使用して、次に来るものを入力します。 ZSHはファジーオートコンプリートをサポートし、それを処理できます。 ( OH-MY-ZSHプラグイン を使用すると、特に便利です。)

1
Martin Thoma

これらの問題のあるファイル名を保持するかどうかは言いませんでした。 1つの解決策は、ファイルの名前を(一部またはすべて)変更して、次のスクリプトを実行して入力できる名前にして、問題を1度「修正」することです。

#!/bin/sh
for old in *
do
      printf "%s ...? " "$old"
      if read new  &&  [ "$new" != "" ]
      then
             mv -i "$old" "$new"
      fi
done

これにより、既存のファイル名がリストされ、それぞれの後に...?が続きます。入力するだけ Enter ファイルをそのまま残します。または、新しい名前を入力して名前を変更します。 -iオプションを使用すると、別の既存のファイルの名前を指定した場合、上書きの確認を求められます。

このスクリプトは、いくつかの方法で変更できます。

  • ワイルドカード(*)をより限定的なもの(たとえば、*.avi *.mov)に変更できるため、すべてのファイルを確認する必要がありません。
  • mvcpに変更すると、ファイルのコピーを現在の名前で保持し、入力可能な名前で(一時的な?)コピーを作成できます。
  • 既存のファイル名に基づいて新しいファイル名を作成できます。例えば、

    if read pfx  &&  [ "$pfx" != "" ]
    then
            mv -i "$old" "$pfx$old"
    fi
    

    これにより、古い名前の前に接頭辞を付けることができます。一意のプレフィックスを選択した場合、これによりオートコンプリートを使用できます。