awkを使用してファイルへのシンボリックリンクのリストを名前にスペースがあるディレクトリに送信しますか?

awkを使用してファイルへのシンボリックリンクのリストを名前にスペースがあるディレクトリに送信しますか?

ソフトリンクを作成したいファイル名のリストがあります(と呼んでくださいfilenames.txt)。使用するとき

cat filenames.txt | awk 'system("ln -s "$0" ~/directory\ with\ spaces/subdirectory/")`

空白(ファイル名またはターゲットディレクトリ)があるエントリがあると、詰まっているようです。

スペースがないことを確認するために、まずすべての名前を変更せずにこれらのソフトリンクを作成するにはどうすればよいですか?

答え1

注:ここに提供されているすべての例は、ファイルとファイル名のリストがあるディレクトリで実行されます。たとえば、/mnt/Hard\ Drive/some\ files/フォルダにある場合は、filenames.txtそのフォルダにも保存されていることを確認してください。シェルでコマンドを実行するときは、cd /mnt/Hard\ Drive/some\ files/最初に例を実行してください。

AWK

AWKのシステムコールは、空白を含むエントリに使用できますが、もう少し複雑です。ソースコードに構文エラーがあり、cat役に立ちません。この事実にかかわらず、正しいアプローチは最初にコマンドをsprintf()変数に書き込んでからその変数をに渡すことですsystem()

$ ls dir\ with\ spaces/                                                                                                  
$ awk '{cmd=sprintf("fpath=$(realpath -e \"%s\" );%s %s \"$fpath\" \"%s\"",$0,"ln","-s","dir with spaces/");system(cmd) }' filenames.txt                   
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

BASH(またはBourneに似たシェル)

抵抗の少ないパスは、以下に示す小さなシェルスクリプトを使用することです。

#!/bin/bash
# uncomment set -x for debugging
#set -x 

# Preferably, the directory should be full path
dir="dir with spaces/"

while IFS= read -r line
do
    if [ "x$line" != "x"  ] && [ -e "$line" ];
    then
        fpath=$( realpath -e "$line" )
        ln -s "$fpath" "$dir"
    fi
done < filenames.txt

実行中のスクリプト:

$ ls -l dir\ with\ spaces/                                                                                        
total 0
$ cat filenames.txt                                                                                               
input.txt
ascii.txt
disksinfo.txt
process info.txt
$ ./makelinks.sh                                                                                                  
$ ls -l dir\ with\ spaces/                                                                                        
total 0
lrwxrwxrwx 1 xieerqi xieerqi  9 1月  15 00:47 ascii.txt -> ascii.txt
lrwxrwxrwx 1 xieerqi xieerqi 13 1月  15 00:47 disksinfo.txt -> disksinfo.txt
lrwxrwxrwx 1 xieerqi xieerqi  9 1月  15 00:47 input.txt -> input.txt
lrwxrwxrwx 1 xieerqi xieerqi 16 1月  15 00:47 process info.txt -> process info.txt

仕組みは簡単です。while IFS= read -r line; do . . . done < filenames.txtfilenames.txt を 1 行ずつ読み込み、各行はline変数に入ります。その行が空でないこと、ファイルが存在することを確認します(スクリプトは元のファイルとファイルのリストを含む同じディレクトリで実行されます)。両方の条件が真の場合はリンクを設定します。

ファイルが改行文字で終わっていることを確認する必要があります。最後の行が改行文字で終わらない場合(実際には発生)、最後の行はスキップされるため、ファイルへのリンクは生成されません。

理想的でも欠点もありませんが、system()これはawkが利用できない場合(最近はまれですが)、実行可能なソリューションです。

パラメータ

少し簡単なシェルアプローチは、 viaxargsbash -c '' shreflag を使って filenames.txt を 1 行ずつ読み、-L1各行を bash の引数として使用することです。コマンドライン引数の配列$@($1、$2、$3...など)を文字列変数に変換してから渡しますln。最後に変数がsh設定されます$0。これが必要かどうかは議論の余地がありますが、この質問の主題ではないのでスキップします:)

$ xargs -I {} -L1 bash -c 'file="$@";fpath=$(realpath -e "$file"); ln -s "$fpath"  ./dir\ with\ spaces/"$file" ' sh < filenames.txt                      
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

Python

$ ls dir\ with\ spaces/ 
$ python -c "import os,sys;fl=[f.strip() for f in sys.stdin];map(lambda x: os.symlink(os.path.realpath(x),'./dir with spaces/' + x),fl)" < filenames.txt
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

仕組みは簡単です。filenames.txtpythonにリダイレクトしstdin、各行をリストに読み込み、リスト内のflos.symlink()エントリを実行します。行が長いが効果的です。

真珠

Perlでは、より短いバージョンが実装されています。

$ perl -lane 'use Cwd "realpath";symlink(realpath($_),"./dir with spaces/" . $_)' < filenames.txt                                                     
$ ls dir\ with\ spaces/                                                                                                  
ascii.txt@  disksinfo.txt@  input.txt@  process info.txt@

答え2

system()これらの操作には、外部コマンドや同様の機能を呼び出すスクリプト言語を使用しないでください。それは同じことを提示します危険と罠Bourne Shellのようなeval

xargs安全で効率的にこれを行うことができます。

xargs -d '\n' ln -s -t ~/directory\ with\ spaces/subdirectory/ < filenames.txt

filenames.txtこれが行うことは、引数として提供するコマンドの最後に何かを追加してコマンドを作成することです。次に実行します。上記の例では、ビルドするコマンドは次のとおりです。

ln -s -t directory file1 file2 ... fileN

興味深いことに、filenames.txtで何も参照する必要はなく、ディレクトリへのネストされた参照を使用する必要もありません。

答え3

文字列リテラルで二重バックスラッシュを使用してシェルに渡された文字列に単一のバックスラッシュをインポートすると、シェルは次の文字を文字通り解釈します。

awk 'system("… ~/directory\\ with\\ spaces/subdirectory/")'

シェルフラグメントで使用される入力を引用する1つの方法は、各文字の前にバックスラッシュを追加することです。 Portable awkではこれを簡単にすることはできません(GNU awkにはこれを行う拡張機能があります)。または、各要素の周囲に単一引用符を配置し、要素のすべての単一引用符をに置き換えます'\''。 Awkを使用すると、このソリューションが簡単になりますgsub。 awkコードスニペットがシェルスクリプトにある場合は、バックスラッシュ8進数エスケープを使用してawk文字列リテラルに一重引用符を入れることができます。

<filenames.txt awk '{
    gsub(/\047/, /\047\\\047\047/, $0);
    system("ln -s \047" $0 "\047 ~/directory\\ with\\ spaces/subdirectory/");
}'

関連情報