ホームディレクトリ全体で無効な権限を持つファイルまたはディレクトリを検索するコマンドを検討してください。
$ find $HOME -perm 777
これは単なる例です。このコマンドは、壊れたシンボリックリンクを一覧表示できます。
$ find $HOME -xtype l
または、長いシンボリックリンクをリストします。
$ symlinks -s -r $HOME
または改行で区切られたパスをstdout
。
これで、ポケットベルから結果を収集できます。
$ find $HOME -perm 777 | less
その後、cd
他の仮想端末の関連ディレクトリに移動します。しかし、私は次のように、各出力行に対して新しいインタラクティブシェルを開くスクリプトを持ちたいと思います。
$ find $HOME -perm 777 | visit-paths.sh
これにより、各ファイルまたはディレクトリを調べ、タイムスタンプを確認し、権限を変更する必要があるか、ファイルを削除する必要があるかを判断できます。
それBashスクリプトを使用できますそれファイルまたは標準入力からパスを読み取る、このように:
#! /usr/bin/env bash
set -e
declare -A ALREADY_SEEN
while IFS='' read -u 10 -r line || test -n "$line"
do
if test -d "$line"
then
VISIT_DIR="$line"
elif test -f "$line"
then
VISIT_DIR="$(dirname "$line")"
else
printf "Warning: path does not exist: '%s'\n" "$line" >&2
continue
fi
if test "${ALREADY_SEEN[$VISIT_DIR]}" != '1'
then
( cd "$VISIT_DIR" && $SHELL -i </dev/tty )
ALREADY_SEEN[${VISIT_DIR}]=1
continue
else
# Same as last time, skip it.
continue
fi
done 10< "${*:-/dev/stdin}"
これには、次のようないくつかの利点があります。
新しい出力行が表示されると、スクリプトは新しいシェルを開きます
stdin
。つまり、ジョブを開始する前に遅いコマンドが完全に完了するのを待つ必要はありません。新しく作成されたシェルでタスクを実行すると、遅いコマンドはバックグラウンドで実行され続けるため、タスクが完了したときは次のパスにアクセスする準備ができます。
false; exit
必要に応じて、Ctrl-C Ctrl-Dなどを使用してループを早く終了できます。このスクリプトはファイル名とディレクトリを処理します。
このスクリプトは、同じディレクトリを2回連続して移動するのを防ぎます。 (連想配列を使用してこれを行う方法を説明した@MichaelHomerに感謝します。)
ただし、このスクリプトには問題があります。
- 最後のコマンドの状態がゼロでない場合、パイプライン全体は終了します。これは早期シャットダウンには便利ですが、通常は偶発的な早期
$?
シャットダウンを防ぐために特別な確認が必要です。
この問題を解決するためにPythonスクリプトを作成しました。
#! /usr/bin/env python3
import argparse
import logging
import os
import subprocess
import sys
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Visit files from file or stdin.'
)
parser.add_argument(
'-v',
'--verbose',
help='More verbose logging',
dest="loglevel",
default=logging.WARNING,
action="store_const",
const=logging.INFO,
)
parser.add_argument(
'-d',
'--debug',
help='Enable debugging logs',
action="store_const",
dest="loglevel",
const=logging.DEBUG,
)
parser.add_argument(
'infile',
nargs='?',
type=argparse.FileType('r'),
default=sys.stdin,
help='Input file (or stdin)',
)
args = parser.parse_args()
logging.basicConfig(level=args.loglevel)
shell_bin = os.environ['SHELL']
logging.debug("SHELL = '{}'".format(shell_bin))
already_visited = set()
n_visits = 0
n_skipped = 0
for i, line in enumerate(args.infile):
visit_dir = None
candidate = line.rstrip()
logging.debug("candidate = '{}'".format(candidate))
if os.path.isdir(candidate):
visit_dir = candidate
elif os.path.isfile(candidate):
visit_dir = os.path.dirname(candidate)
else:
logging.warning("does not exist: '{}'".format(candidate))
n_skipped +=1
continue
if visit_dir is not None:
real_dir = os.path.realpath(visit_dir)
else:
# Should not happen.
logging.warning("could not determine directory for path: '{}'".format(candidate))
n_skipped +=1
continue
if visit_dir in already_visited:
logging.info("already visited: '{}'".format(visit_dir))
n_skipped +=1
continue
elif real_dir in already_visited:
logging.info("already visited: '{}' -> '{}'".format(visit_dir, real_dir))
n_skipped +=1
continue
if i != 0:
try :
response = input("#{}. Continue? (y/n) ".format(n_visits + 1))
except EOFError:
sys.stdout.write('\n')
break
if response in ["n", "no"]:
break
logging.info("spawning '{}' in '{}'".format(shell_bin, visit_dir))
run_args = [shell_bin, "-i"]
subprocess.call(run_args, cwd=visit_dir, stdin=open('/dev/tty'))
already_visited.add(visit_dir)
already_visited.add(real_dir)
n_visits +=1
logging.info("# paths received: {}".format(i + 1))
logging.info("distinct directories visited: {}".format(n_visits))
logging.info("paths skipped: {}".format(n_skipped))
Continue? (y/n)
ただし、生成されたシェルにプロンプト応答を渡すにはいくつかの問題があり、次のy: command not found
問題が発生しているようです。
subprocess.call(run_args, cwd=visit_dir, stdin=open('/dev/tty'))
stdin
使用時に他の作業を行う必要がありますかsubprocess.call
?
それとも、私が聞いたことのないこれらの2つのスクリプトを重複させるための広く利用可能なツールはありますか?
ベストアンサー1
Bashスクリプトは期待どおりにすべてのことをするようです。インタラクティブシェルを作成するサブシェルの後にのみ存在する必要があります|| break
。これにより、対応するインタラクティブシェルがシャットダウンし、誘導されたエラー(たとえば、Ctrl+C次Ctrl+Dまたはexit 1
コマンド)が発生した場合は、パイプライン全体でシャットダウンされます。
もちろん、指摘したように、対話型シェルで使用された最後のコマンドが(不要な)エラーで終了したときにも終了しますが、次のように単純なコマンドを実行してこれを実行できます:
。最後のコマンド何よりも先に正常終了Ctrl+Cあるいは、パイプライン全体をシャットダウンする唯一の許容可能な方法でテストすることによって(潜在的に良い解決策として)、|| { [ $? -eq 130 ] && break; }
つまり対話型シェルを作成するサブシェルの後に使用されます。|| break
連想配列がまったく必要ない、より簡単な方法で、次のようにuniq
出力を-ingできます。find
find . -perm 777 -printf '%h\n' | uniq | \
(
while IFS= read -r path ; do
(cd "${path}" && PS1="[*** REVISE \\w]: " bash --norc -i </dev/tty) || \
{ [ $? -eq 130 ] && break; }
done
)
もちろんこれfind
を行うには、連続した重複エントリ(存在する場合)を生成できる名前ソースが必要です。またはsort -u
、並べ替える代わりに使用することもできますが、最初の対話型シェルの作成を見る前に完了するuniq
まで待つ必要がありますsort
が、これは望ましくないようです。
次に、Pythonスクリプトの方法を見てみましょう。
どのように呼び出すかは言っていませんが、次のようにパイプを介して使用する場合:
names-source-cmd | visit-paths.py
input()
次に、名前入力とPython関数入力という2つの矛盾する目的でstdinを使用しています。
その後、次のようにPythonスクリプトを呼び出すことができます。
names-source-cmd | visit-paths.py /dev/fd/3 3<&0 < /dev/tty
上記の例で行われたリダイレクトに注意してください。まず、作成したパイプ(パイプのその部分でstdinになる)を任意のファイル記述子3にリダイレクトし、次にPythonスクリプトが使用できるようにstdinをttyとして開きます。そのinput()
機能のため。その後、ファイル記述子3はPythonスクリプトの引数を介して名前のソースとして使用されます。
次の概念証明を考慮することもできます。
find | \
(
while IFS= read -ru 3 name; do
echo "name is ${name}"
read -p "Continue ? " && [ "$REPLY" = y ] || break
done 3<&0 < /dev/tty
)
上記の例では、同じリダイレクトトリックを使用しています。したがって、連想配列に示されているパスをキャッシュし、新しく表示された各パスに対話型シェルを生成する独自のBashスクリプトでそれを使用できます。