web-dev-qa-db-ja.com

ベース(サフィックスなし)ファイル名の最後の3文字を抽出する最短の方法

Shスクリプトの変数をファイルのベース名の最後の3文字に設定しようとしています(ベース名とは、パスなしおよびサフィックスなし) 。私はこれに成功しましたが、純粋に好奇心から、私が使用できるより短い単一のコマンドがあるかどうか疑問に思っています。元々私はawkのワンライナーを持っていましたが、それはかなり長かったです。現在、私はこの2行のスクリプトを持っています(完全なファイル名が$1にあると仮定):

filebase=`basename "$1"`
lastpart=`echo -n ${filebase%.*} | tail -c3`

したがって、たとえば、 "/ path/to/somefile.txt"$lastpart "ile"で終わります。

どういうわけかbasenameとビットを組み合わせてサフィックスを単一のコマンドにストリップできますか?パイプを使用せずにそれをtail(または私が使用できる何か)に送信する方法はありますか?接尾辞は不明なので、basenameのパラメータとして使用することはできません。

主な目標は、実際にはできるだけ短くして一目で読めるようにすることではありません。これらすべての実際のコンテキストは スーパーユーザーに関するこの質問 です。ここでは、かなり単純な答えを考え出そうとしています。

12
Jason C

これはexprの典型的な仕事です:

_$ file=/path/to/abcdef.txt
$ expr "/$file" : '.*\([^/.]\{3\}\)\.[^/.]*$'
def
_

ファイル名が予想される形式(ドットが1つだけで、ドットの前に少なくとも3文字が含まれている)であることがわかっている場合は、次のように簡略化できます。

_expr "/$file" : '.*\(.\{3\}\)\.'
_

一致がない場合、および一致した部分が0に解決される数値である場合、終了ステータスはゼロ以外になることに注意してください。(_a000.txt_または_a-00.txt_など)

zshの場合:

_file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}
_

(_:t_ for tail(basename)、_:r_ for rest(extensionを削除した場合)).

6
var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

最初に$varから最後の3文字を削除し、次に$varからその削除の結果を削除します。これにより、$varの最後の3文字が返されます。以下は、そのようなことをどのように行うかを示すことを具体的に目的としたいくつかの例です。

touch file.txt
path=${PWD}/file.txt
echo "$path"

/tmp/file.txt

base=${path##*/}
exten=${base#"${base%???}"}
base=${base%."$exten"}
{ 
    echo "$base" 
    echo "$exten" 
    echo "${base}.${exten}" 
    echo "$path"
}

file
txt
file.txt
/tmp/file.txt

それほど多くのコマンドでこれをすべて広げる必要はありません。これを圧縮できます:

{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

$IFSsettingと組み合わせることも、シェル変数を解析してドリルする非常に効果的な方法です。

(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")

これにより、/の最後の$pathに続く最初のピリオドの直前の3文字のみが取得されます。 .の最後の$pathの直前の最初の3文字のみを取得する場合(たとえば、ファイル名に複数の.が含まれている可能性がある場合)

(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")

どちらの場合でも、次のことができます。

newvar=$(IFS...)

そして...

(IFS...;printf %s "$2")

....に続くものを出力します

外部プログラムを使用してもかまわない場合は、次のことができます。

printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

ファイル名に\newline文字が含まれる可能性がある場合(ネイティブシェルソリューションには適用されません-とにかくすべて処理されます)

printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'
13
mikeserv

Perlを使用できる場合:

lastpart=$(
    Perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)
4
cuonglm

sedはこれで動作します:

[user@Host ~]$ echo one.two.txt | sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|'
two

または

[user@Host ~]$ sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|' <<<one.two.txt
two

sed-rをサポートしていない場合は、()のインスタンスを\(および\)に置き換えるだけで、-rは必要です。

2
BenjiWiebe

Perlが利用可能な場合、他のソリューションより読みやすくなる可能性があります。特に、正規表現言語の方が表現力があり、/x修飾子。これにより、より明確な正規表現を記述できます。

Perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

そのような一致がない場合(ベース名に拡張子がない場合、または拡張子の前のルートが短すぎる場合)は、何も出力されません。要件に応じて、正規表現を調整できます。この正規表現は制約を適用します。

  1. これは、最後の拡張子の前の3文字(最後のドットの後の最後のドットを含む部分)と一致します。これらの3文字にはドットを含めることができます。
  2. 拡張子は空にすることができます(ドットを除く)。
  3. 一致する部分と拡張子は、ベース名の一部(最後のスラッシュの後の部分)でなければなりません。

コマンド置換でこれを使用すると、末尾の改行が多すぎるという通常の問題があります。これは、ステファンの回答にも影響する問題です。どちらの場合でも対処できますが、ここでは少し簡単です。

lastpart=$(
  Perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline

Python2.7

$ echo /path/to/somefile.txt | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
ile

$ echo file.one.two.three | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
two
0
HVNSweeting

このbash関数、pathStr()は、あなたが探しているものを実行すると思います。

Awk、sed、grep、Perl、exprは必要ありません。組み込みのbashのみを使用しているため、非常に高速です。

依存するargsNumberおよびisOption関数も含めましたが、それらの機能は簡単にpathStrに組み込むことができます。

依存関数ifHelpShowは、ターミナルコマンドラインまたは [〜#〜] yad [〜#〜] を介してGUIダイアログボックスにヘルプテキストを出力するための多数の従属関係があるため、含まれていません。渡されたヘルプテキストは、ドキュメントに含まれています。 ifHelpShowとその依存パッケージが必要かどうかをアドバイスします。

function  pathStr () {
  ifHelpShow "$1" 'pathStr --OPTION FILENAME
    Given FILENAME, pathStr echos the segment chosen by --OPTION of the
    "absolute-logical" pathname. Only one segment can be retrieved at a time and
    only the FILENAME string is parsed. The filesystem is never accessed, except
    to get the current directory in order to build an absolute path from a relative
    path. Thus, this function may be used on a FILENAME that does not yet exist.
    Path characteristics:
        File paths are "absolute" or "relative", and "logical" or "physical".
        If current directory is "/root", then for "bashtool" in the "sbin" subdirectory ...
            Absolute path:  /root/sbin/bashtool
            Relative path:  sbin/bashtool
        If "/root/sbin" is a symlink to "/initrd/mnt/dev_save/share/sbin", then ...
            Logical  path:  /root/sbin/bashtool
            Physical path:  /initrd/mnt/dev_save/share/sbin/bashtool
                (aka: the "canonical" path)
    Options:
        --path  Absolute-logical path including filename with extension(s)
                  ~/sbin/file.name.ext:     /root/sbin/file.name.ext
        --dir   Absolute-logical path of directory containing FILENAME (which can be a directory).
                  ~/sbin/file.name.ext:     /root/sbin
        --file  Filename only, including extension(s).
                  ~/sbin/file.name.ext:     file.name.ext
        --base  Filename only, up to last dot(.).
                  ~/sbin/file.name.ext:     file.name
        --ext   Filename after last dot(.).
                  ~/sbin/file.name.ext:     ext
    Todo:
        Optimize by using a regex to match --options so getting argument only done once.
    Revised:
        20131231  docsalvage'  && return
  #
  local _option="$1"
  local _optarg="$2"
  local _cwd="$(pwd)"
  local _fullpath=
  local _tmp1=
  local _tmp2=
  #
  # validate there are 2 args and first is an --option
  [[ $(argsNumber "$@") != 2 ]]                        && return 1
  ! isOption "$@"                                      && return 1
  #
  # determine full path of _optarg given
  if [[ ${_optarg:0:1} == "/" ]]
  then
    _fullpath="$_optarg"
  else
    _fullpath="$_cwd/$_optarg"
  fi
  #
  case "$_option" in
   --path)  echo "$_fullpath"                            ; return 0;;
    --dir)  echo "${_fullpath%/*}"                       ; return 0;;
   --file)  echo "${_fullpath##*/}"                      ; return 0;;
   --base)  _tmp1="${_fullpath##*/}"; echo "${_tmp1%.*}" ; return 0;;
    --ext)  _tmp1="${_fullpath##*/}";
            _tmp2="${_tmp1##*.}";
            [[ "$_tmp2" != "$_tmp1" ]]  && { echo "$_tmp2"; }
            return 0;;
  esac
  return 1
}

function argsNumber () {
  ifHelpShow "$1" 'argsNumber "$@"
  Echos number of arguments.
  Wrapper for "$#" or "${#@}" which are equivalent.
  Verified by testing on bash 4.1.0(1):
      20140627 docsalvage
  Replaces:
      argsCount
  Revised:
      20140627 docsalvage'  && return
  #
  echo "$#"
  return 0
}

function isOption () {
  # isOption "$@"
  # Return true (0) if argument has 1 or more leading hyphens.
  # Example:
  #     isOption "$@"  && ...
  # Note:
  #   Cannot use ifHelpShow() here since cannot distinguish 'isOption --help'
  #   from 'isOption "$@"' where first argument in "$@" is '--help'
  # Revised:
  #     20140117 docsalvage
  # 
  # support both short and long options
  [[ "${1:0:1}" == "-" ]]  && return 0
  return 1
}

[〜#〜]リソース[〜#〜]

0
DocSalvager