シェルスクリプト総合 その32
■ このスレッドは過去ログ倉庫に格納されています
シェルスクリプトに関する総合スレッドです。 全般 ・荒しは無視しましょう。 ・丁寧な姿勢を心掛けましょう。 ・ネチケット(死語)を意識しましょう。 ・「○○(他の言語)でいいやん」は禁止。他のスレに行ってください。 シェルスクリプト総合 その31 https://mevius.5ch.net/test/read.cgi/tech/1565446670/ rename 関係はperlで試せる dry run 作ってる https://pastebin.com/0uJ462jq touch "file(1).txt"; ~/bin/rename.pl dry 'tr/[()]/_/' *txt file(1).txt => file_1_.txt ~/bin/rename.pl run 'tr/[()]/_/' *txt; ls file_1_.txt find . -name '*[ ()]*' -print | sort -r | while read item; do mv "$item" "${item%/*}/$(echo "${item##*/}" | tr ' ()' '_')" done Ruby で作った。 コードは次のレスに書く DryRun を使ったので実際には、変更されない ファイル/ディレクトリは、同時に変更すると、バグるかも知れないので、別々に変更する。 また「子ディレクトリ(1)/孫ディレクトリ(1)」のように、2か所以上同時に変更すると、バグるかも知れない こんな複雑なものを、シェルスクリプトで書くのは超危険! 特に移動は、dest が存在するときは移動になり、 存在しないときは変更になるという、極めて難しい場合分けが必要で、 処理の途中でエラーになると、エラーまでの処理が確定してしまうから、中途半端で巻き戻せないから、 必ず、親ディレクトリ以下のバックアップを取っておく! require 'fileutils' root_dir = "C:/Users/Owner/Documents/Ruby/test/**/*" # 基準ディレクトリ # 変更前のファイル/ディレクトリを入れる配列 src_files = [ ]; src_dirs = [ ] # 基準ディレクトリ以下のファイル/ディレクトリを取り出して、配列に入れる Dir.glob( root_dir ) do |path| case when File.file?( path ) then src_files.push ( path ) when File.directory?( path ) then src_dirs.push ( path ) else # 処理しない end end def change_paths( src_ary ) # ファイル/ディレクトリ名を変更する src_ary.each do |src_path| dest_path = src_path.tr( " ()", "_" ) # 変換 next if src_path == dest_path # 変換されなかった場合は、処理しない # 変更後の名前のファイル/ディレクトリが、既に存在すれば、エラー msg = "変更後の名前のファイル/ディレクトリが、既に存在します!\n#{ dest_path }" raise msg if File.exist? ( dest_path ) FileUtils::DryRun.move( src_path, dest_path ) end end change_paths( src_dirs ) change_paths( src_files ) なんかかえってめんどくさいことしてんな。perlでめんどくさく書きたいだけ? mv を作った奴は、頭おかしいw dest が存在するときは移動になり、 存在しないときは名前の変更になるという、極めて難しい場合分けが必要だから 漏れらが欲しいのは、 rename だけする関数と、移動するだけの関数の、2つに分かれている関数 それが分かれていないから、自分で処理を分けて、 かなりチェックしないと、バグってしまう 一括リネームってたいてい複雑になるから GUIツールを使ったほうが良いと思うんだよね Windows 10版のPowerToysにリネーム機能が追加 https://pc.watch.impress.co.jp/docs/news/1215602.html >>112 Fix(IFS文字が頭と末尾にある場合。dirname,basenameにしたのは気分)&Variation find . -name '*[ ()]*' -print | sort -r | (IFS=''; while read item; do mv "$item" "$(dirname "$item")/$(basename "$item" | tr ' ()' '_')" done) find . -name '*[ ()]*' -print | sort -r | (IFS=''; while read item; do dir="$(dirname "$item")"; newname="$(basename "$item" | tr ' ()' '_')"; newpath="$dir/$newname" [ ! -e "$newpath" ] && mv "$item" "$newpath" || echo "can't rename $item, exists $newname" >&2 done # can't rename $item 〜 メッセージは単なる目安 find . -name '*[ ()]*' -print | sort -r | (IFS=''; while read item; do dir="${item%/*}"; newname="$(tr ' ()' '_' <<< ${item##*/})" [ -e "$dir/$newname" ] && { name="${newname%.*}_" ext="${newname#*.}"; [ "$ext" = "$newname" ] && ext='' || ext=".$ext" while [ -e "$dir/$name$ext" ]; do name+='_' done newname="$name$ext" } mv "$item" "$dir/$newname" done) >>120 誤 ext="${newname#*.}" 正 ext="${newname##*.}" >>111 rename自体がperlだしちゃんと試行もあるけど >>122 perl ワンライナーより dry run のスイッチと どのように変更させるかに集中し 出力確認などの余計なことは共通する定型処理だから コードにまとめたほうが使いやすい Linuxディストリによっては標準で入っている、perlスクリプトで作られたrenameコマンドのことだろ renameは標準で入ってないとはいえ テスト無しの自作コードでやるような処理じゃないやろ find -depth -exec rename -nでdry-runしてから-n消せばいい dry-runって言っても結局ひとつをrename/mvするその時点での状態でしか判断してないし、mvコマンドの前にecho付けるのと何が違うのだか [ "$1" = "-n" ] && nac=echo || nac='' $nac mv ... でスイッチにしたけりゃ同じようにできるだろし 極めて難しいらしいことも結局時点での存在チェックしてるだけで、シェルスクリプトでtestを使うのと何がそうも極めて難しいほど違うのか >>122 既に存在してたらエラー(オプションによって強行もできる)でもあるよね The original C<rename> did not check for the existence of target filenames, so had to be used with care. I hope I've fixed that (Robin Barker). って、わざわざコメント書くようにな目玉なw どのバージョンを使ってるのかわからんけど、パッケージによってはものすごいオプション拡張されたのが使われてるねえ Windows10, WSL, Ubuntu 16.04 にも、/usr/bin/rename がある この手の処理の何がヤバイかと言うと、 例えば、3つ目の処理で、エラーが出ると、 2つ目までは、変更されてしまっているから、 そこから再実行すると、最初から実行していた時と、初期条件が変わっている データベースと同じで、一部分だけ更新されたような、中途半端な状態になってはいけない(一貫性)。 エラー時には、すべてをロールバックするべき。 または、全体のバックアップを取っておく すべてを更新するか、すべてをロールバックするのどちらかの状態だけにする >>108 まだ見てくれてる? 変更するんじゃなくて新たに作るほうが無難だと思うよ。 新たに作るといっても通常ファイルはハードリンクできるからね。 実際にやってみたよ。 $ find a a a/a (0) a/a (0)/a (00) a/a (0)/a (01) a/a (1) a/a (1)/a (11) a/a (1)/a (10) $ find b b $ cd a $ find * -type d | perl -ne 'chop; s/[ ()]/_/g ; mkdir("../b/$_") || die("$!:$_");' $ find * -type f | perl -ne 'chop; $old = $_; s/[ ()]/_/g ; link( $old,"../b/$_") || die("$!:$_");' $ cd .. $ find b b b/a__1_ b/a__1_/a__10_ b/a__1_/a__11_ b/a__0_ b/a__0_/a__00_ b/a__0_/a__01_ $ 途中で何か起こっても起こらなくても a の方は無傷だからね。好きなだけやり直せるよ。 renameコマンドって複数ないか? 's/パターン/置換文字/' FILEとパターン 置換文字 FILEとサイトによって説明違うんだよな 俺が散らばったファイルをリネームする時はmoreutilsのvidir使ってるけど テキストエディタで修正する奴な Larry Wall and Robin Barker版とそれの機能拡張版のAristotle Pagaltzis版じゃね Aristotle Pagaltzisで増えた -s パターン 置換文字 FILE 使ってじゃないの >>132 util-linux版とperl同梱版(これはさらにいくつかに分かれる)の違い debian/ubuntu なら前者rename.ul後者rename(Larry Wall版) centos なら前者rename後者prename(Peder Stray版) >>132 Debian系はPerl付属のprename、 CentOS系はutil-linuxのrename.ulをrenameとして使ってるらしい そら人によって言ってることが違う筈だ Larry Wall - Robin Barker, 重複時実行しない、 重複時実行強制実行オプション -- Aristotle Pagaltzis, 機能いろいろ (>>132 のはそのうちの -s/--subst from to じゃないの) - Peder Stray, 重複時バックアップオプション Aristotle Pagaltzis意外はそんなに変わらんかな。Aristotle Pagaltzisで追加した機能を使わなければAristotle Pagaltzisも https://github.com/tldr-pages/tldr/issues/3125 Debian prename from pkg perl: Robin Barker & Larry Wall. Debian/Ubuntu file-rename from pkg file-rename: Larry Wall. Arch perl-rename from pkg perl-rename: Peder Stray (same as Fedora). Fedora prename from pkg prename: Peder Stray (same as Arch). macOS rename from pkg rename: Aristotle Pagaltzis. らしいけど、Larry Wall版ってあるのか?Ubuntu は Larry Wall and Robin Barker だな たぶん基本的なことだと思うんですが、教えてください。 #!/bin/bash echo -e \ 'Hostname\tIP\tInterface\tVlan\tMode\tPort-Channel\tAllowed\sVlan\tDescription'\ > cp.tsv このスクリプトを実行すると… $cat cp.tsv -e Hostname IP Interface Vlan Mode Port-Channel Allowed Vlan Description このように、先頭に-eが入ってしまいます。なぜでしょう…? Perl同梱(?)のprename/file-renameがLarry Wall版か Ubuntuで pkg rename (pkg file-rename??)を入れるとprename/file-renameもLarry Wall and Robin Barker版に上書きされる感じかな?? >>138 いや、違った Perl同梱(?)のprename/file-renameもLarry Wall and Robin Barker版か >>137 そのスクリプトを x.sh だとして、 $ /bin/sh x.sh とかして実行したんじゃない? >>137 -eをオプションとして扱うかどうかはシェルによって違う。 bashはオプションだが、dashはオプションではなくただの文字列 >>140-141 あ、ほんとだ。 sh -xで実行してました。 bash -xだと大丈夫でした。 シェバン付けてるから大丈夫だと思い込んでました。ダメなんですね。 横着せずにきちんと打つようにします。 ありがとうございました! x-y ↑こういう形式の,ハイフンで結ばれた二つの数字の計算結果の正負だけを知りたいのですが, なるべく処理が早い方法ないですか。 対象の文字列は標準出力ではなくて変数に格納されています。 思い付いたのは [ ${var%-*} -lt ${var#*-} ] か $((var < 0)) です。 どっちもPOSIXには準拠している筈ですが,同じような処理を大量に繰り返すとどうしても遅くなります。 これがシェルスクリプトの限界なら,仕方ないのですが,もう少し最適化できそうな気もします。 どうかお知恵とお力を貸してください。 $((var < 0))はPOSIX準拠じゃなくね? やるなら [ $(($var)) -lt 0 ] こうだろうけど それでも遅いって言うならどうしようもないと思うが、 気になるほど遅いなら別のところに問題があるんじゃね? ちなみにうちのマシンだとこんな感じ $ time sh -c 'var=1-2; i=0; while [ $i -lt 1000000 ]; do [ $(($var)) -lt 0 ]; i=$((i+1)); done' real 0m2.722s user 0m2.718s sys 0m0.004s 100万やって3秒、1万回だと0.03秒。これが気になるとしたら 相当多くの数の計算をしてることになるはずなんだが本当に? bcプロセス起動してるから遅い それは実行しなくてもわかる あとオマケ、速いとは思えないけど IFS=- var=1-2 set -- $var echo $1 # 1 echo $2 # 2 これを使ってもできる。 速さは [ ${var%-*} -lt ${var#*-} ] とどっこいどっこいだろうな もしかしたらset使ったほうが速いかもしれない程度 >>146 それ100万やったら50秒かかったわ 10万で5秒だしawk使うわ >>150 えらい遅いなw 俺のなんて2012年発売のCPUだぞ。 i7 3770。3.40Ghz(ブースト時で3.9Ghz) i7 3.4Ghzって今どき低スペックなのか? ハイスペックかと思ってた >>150 awkつかうなら、awkのコード書いてみてくれ 実行してみる。 >>152 スペックは低くはないと思うけどさ、もう古いし 中古だと2〜3万円台で搭載パソコンを買えちゃうよ https://used.dospara.co.jp/sale/search.php?view=1& ;kw=Core%20i7%203770&page=1 >>154 凄い時代になったなw いつのまにか皆さんハッピーになってた バックグラウンドで色々動きっぱなし(usage 60%ぐらい)のWindows PC(Ryzen 5 2600, 3.66GHz)で Cygwin real 0m23.706s user 0m23.375s sys 0m0.093s WSL real 0m3.987s user 0m3.891s sys 0m0.016s だった。えっ、私のCygwin遅すぎ・・・ >>156 うちもcygwinだとそんな感じだわw msysも WSLは全然速度変わらないのにな 本質的にCPUだけで処理できる内容のはずだが なにかシステムコール呼び出してるんだろうな >>153 $ printf "%d-%d¥n" $(shuf -r -i 0-999 -n 1000000) $(shuf -r -i 0-999 -n 1000000) > input.txt $ time awk -F "-" '{ print (($1-$2) > 0) ? "+"$0 : " "$0 }' < input.txt > output.txt 1行目は0-999でランダムな引き算を100万件生成してファイル出力 2行目の処理は50秒かかったのと同じマシンで1.7~1.8秒で終わる 計算結果をキャッシュするからか10種類くらいの引き算の使い回しだと 100万件でも0.17秒くらいになる >>158 円文字化けしてんぞ $ time awk -F "-" '{ print (($1-$2) > 0) ? "+"$0 : " "$0 }' < input.txt > output.txt real 0m0.373s user 0m0.300s sys 0m0.005s $ time sh -c 'while read var; do [ $(($var)) -gt 0 ] && echo "+$var" || echo " $var"; done' < input.txt > output2.txt real 0m8.247s user 0m4.337s sys 0m3.904s まあ速いんだろうけどさ。想定の範囲内かな。 ランダムな引き算生成するawkバージョン $ awk 'BEGIN{srand(); for (i=0; i<1000000; ++i) {print int(rand()*1000)"-"int(rand()*1000)}}' > input.txt お、こっちの方がわずかに速かった。スクリプトで計算させるよりも読み込み時に分割させたほうが良いんだろうな。 setを使って分割した場合は、setの呼び出しでコストがかかると思うけど $ time sh -c 'IFS=-; while read i j; do [ $i -gt $j ] && echo "+$i-$j" || echo " $i-$j" ; done' < input.txt > output2.txt real 0m7.866s user 0m4.114s sys 0m3.744s $ time sh -c 'var=1-2; i=0; while [ $i -lt 1000000 ]; do [ $(($var)) -lt 0 ]; i=$((i+1)); done' real 0m2.268s user 0m2.264s sys 0m0.004s $ time { var=1-2; echo "i=0;while(i<=1000000){${var}<=0;i++;}" | bc > /dev/null; } real 0m1.540s user 0m1.175s sys 0m0.366s > 対象の文字列は標準出力ではなくて変数に格納されています。 お前らこの条件忘れてるだろw うーん、よく考えるとこれ実際の要件を想定して 「それぞれのコマンドを使って処理をした結果を元に続きの処理をする」となると 差があまりなくなってしまうかもしれん $ time sh -c 'while read var; do echo $(($var)); done' < input.txt | while read i; do [ $i -lt 0 ]; done real 0m7.678s user 0m9.059s sys 0m4.427s $ time awk -F "-" '{ print $1-$2 }' < input.txt | while read i; do [ $i -lt 0 ]; done real 0m7.629s user 0m6.358s sys 0m1.580s $ time bc < input.txt | while read i; do [ $i -lt 0 ]; done real 0m7.649s user 0m6.869s sys 0m2.006s $ time sh -c 'while read var; do [ $(($var)) -gt 0 ] && echo + || echo; done' < input.txt | while read i; do [ "$i" ]; done real 0m7.596s user 0m9.484s sys 0m5.123s $ time awk -F "-" '{ print (($1-$2) > 0) ? "+" : "" }' < input.txt | while read i; do [ "$i" ]; done real 0m5.418s user 0m4.670s sys 0m1.012s 結果をファイルに吐き出すだけなら良いんだけどね、 その結果をシェルスクリプトを使って処理する場合の話 >>151 今年出た i5 9500 プロセッサー・ベース動作周波数 3.00 GHz ターボ・ブースト利用時の最大周波数 4.40 GHz あんまり変わらない気がする シェルスクリプトなんかスレッド使わないだろうし >>167 スレッドバリバリ使ってるぞw | つかうと必然的にスレッドになるしな >>170 今はCPUの話だからハイパースレッディングのスレッドだろう >>169 POSIX準拠じゃないのでは?と言ってるのは $((var < 0)) のvarは数値限定ってところだよ >>171 > | つかうと じゃないの。普通は子プロセスだろう プロセスが別のコアで動くとかか?? スレッドをハイパースレッディングのスレッドを指すなんて初めてみた >>173 文句は>>167 に言ってくれ。合わせただけ そこまで出されるとマジでスレッドという意味で使ってたんだな OSも知らないスレッドだろう、OSから見てもただのコアだろう >>167 涙目www でよろしいか? 俺に言うなって 一行目なんて書かなきゃならないのね >>167 もそんなスレッドのことを言ってないだろうに。なんでかなw プロセスじゃなくスレッドならプロセス起動する無駄時間がないとかそういう意味でのだろうし、そもそもスレッドはそういうために 元々のネタがプロセス起動にどれだけ食われてるのかという話だな、するなら >>172 なるほど。確かに準拠してませんでした,すいません。 >>181 シェルスクリプトでスレッドってどういう事? スレッド使わなくてもコアは使うじゃん。 >>182 なんでオレに聞くんだよ シェルスクリプトはスレッド使わないって当然のこと言っただけじゃないか なんで頭悪い奴が絡んでくるんだよ気持ち悪いな CPUのスペック比較とシェルスクリプトでスレッド使わないという話とどう関係あるの? 新しいCPUはOSスレッドを効率的に実行できる機能でもあるの? そうだよな。CPUの話をしていたのに なんでスレッド使うかどうかの話をしたのかだよな > 今年出た i5 9500 > プロセッサー・ベース動作周波数 3.00 GHz > ターボ・ブースト利用時の最大周波数 4.40 GHz > あんまり変わらない気がする > > シェルスクリプトなんかスレッド使わないだろうし スレッド使わなくてもコア使うから関係あるだろって言えばよかったのか? i5 9500の名前を出したのは、コア6スレッド6だからだろ? >>184 最新のCPUにしたところで周波数がちょっと早くなった程度で差が無いって話 新しいCPUは同時実行スレッド数が増えてるとか言う奴がいるけどシェルスクリプトには関係ないだろ? 「どう関係あるの? 」とか言われても「関係ない」って最初から書いてんのに読めない訳? >>186 頭悪いなー シェルスクリプトで使うのは1コアだけだろうが 本気でシェルスクリプトとコアが関係ないと思ってるのか? パイプ使ったら複数コア使うことになるんだが 例えばcat ファイル名 | grep なんとか ってやると catのファイル表示と、grepが並列で動く あとバックグラウンド実行もあるよね 自分が並列で動くものを書いてないからって、 シェルスクリプトと並列が関係ないわけじゃないだろ 自分が書かないって話をするならC言語だってコアは関係ない それはプロセスレベルの並列化のお話。スレッドの話はどこいった? シェルスクリプトで外部コマンドを使わない場合の話でもしてるのか? シェルスクリプトでパイプを使わないならコアは関係ない! そんな事ありえないけど関係ないんだ! みたいな話したってしょうがないだろ それにパイプ使うのは foo | bar | baz みたいなシェル関数でも使えるし シェル関数の中身はそれぞれこんな感じな while read line do 省略 done < ファイル名 外部コマンド一切なし >コア6スレッド6だからだろ それを言うならスレッド12だろ。ハイパースレッディングでスペック表すときの論理コア数をスレッドと言っているにすぎないだろうに いつものやつが絡んでるだけだろうな、すぐに話を逸らすことからも >>183 まあ、シェル自体はスレッドを使ってることもあるだろう、いまどき シェルスクリプトを書いてどこがスレッドとか全然わからん、どうスクリプト書けばスレッドになるのかなんて全然わからないとこではあるので、そういう意味ではスレッド使わないと言えるだろうけど また、>>146 にスレッドの効果なんて期待できないだろう。子プロセスがんがん起動しているわけでもなさそうだし、並列化なんてできそうもないスクリプトのようだからね 使ってる環境のshの実態がなんによるかが大きいんじゃないのかな?bashはかなり遅い感じかなあ >>195 ,183 >まあ、シェル自体はスレッドを使ってることもあるだろう、いまどき いや、いまどきも1シェルプロセスは1スレッドしかなのか。仮にスレッド使う場合があったとしてもどういう場合というのはわからんな 確認せずにいまどきならと書いた、すまん >>194 i5 9500は6コア6スレッド なんでこういつも思い込みで発言するかな? URL貼れないから「インテルR Core? i5-9500 プロセッサー」からググれ >>195 OSのスレッドは使ってなくても、 CPUのコア数が多ければ、シェルスクリプトでそれを使えるから コア数の数はシェルスクリプトでも意味があるよって話 >>105 > なんでこういつも思い込みで発言するかな? そりゃそうやろw ただの1-2の計算なんか、どんな言語使っても スレッドの効果なんてねーよw 言語じゃなくてやる処理の問題 ちなみに>>164-165 とかはパイプを使ってるのでコア数は意味がある 例えばこれだとshプロセスが2つ起動して、それぞれCPUコアを使っている time sh -c 'while read var; do echo $(($var)); done < input.txt | while read i; do [ $i -lt 0 ]; done' 自己文書化(?)されたシェルスクリプトの例 http://www.tip.net.au/wiki/index.php/Self_documenting_bash_script_options もうちょっと洗練して、例えばhelp関数から実行するときはgettoptsコマンドを ヘルプ出力用に書換えたものにするなんてこともできそう。 >>201 それ何が嬉しいん? ロングオプションがあればいいだけの話? $ seq 1 inf | tail を実行して別のターミナルで $ top -1 を実行してみる。f キーを押して fields management 画面に進んで "Last Used Cpu" を選んで表示させる様にするとプロセスがどの CPUコアで実行されているか表示される >>202 わざわざヘルプを別個に書かないでも gettopts ... case ... ↑この行を解析できるようになれば 半自動的にヘルプが生成できる(のでは)ってことじゃない? >>197 ああ、ハイパースレッディングなんてないCPUか。そりゃすまん わざわざコア6スレッド6なんて書く必要ないだろう、そのスレッドの所以たるハイパースレッディングなんてないんだから、ただのコア6だな ハイパースレッディングがあるCPUと並列したりする場合にあった方がハイパースレッディングが無いという意味でわざわざ書くぐらいだろう ハイパースレッディングなんて名前が悪いなw(まあ、CPU内部でやってることはスレッディングなんだろけど) >>199 言い足りなかったのねwやはりいつものか >>204 ああ、ヘルプの自動生成か。悩むところだよね。 簡単なのなら確かにそのリンク先みたいにできるだろうけど 時々例外的なコードはいるしさ、 とういうかリンク先、なんであんなよくわからんコードなんだ? もっと簡潔にかけそうなんだが txmp.txt? zenity?ん??GUI表示するため? >>205 コア6(ハイパースレッドなし) と書くよりも コア6 スレッド6 の方が明確で短いだろw 試しに書いてみようかと思ってコピペしようとしたら画像やしw >>207 今回のハイパースレッディングでのスレッドを言い出し絡みまくりからは、ないな >>188 >頭悪いなー >シェルスクリプトで使うのは1コアだけだろうが えっ? >>195 >また、>>146 にスレッドの効果なんて期待できないだろう えっ? 話が噛み合ってなかった理由がわかってみんなスッキリだね ■ このスレッドは過去ログ倉庫に格納されています
read.cgi ver 07.5.5 2024/06/08 Walang Kapalit ★ | Donguri System Team 5ちゃんねる