बेस (माइनस प्रत्यय) फ़ाइल नाम के अंतिम 3 वर्णों को निकालने का सबसे छोटा तरीका


12

मैं (आधार नाम मैं पथ के बिना मतलब द्वारा एक फ़ाइल के आधार नाम के अंतिम 3 अक्षरों के लिए एक श लिपि में एक चर सेट करने के लिए कोशिश कर रहा हूँ और प्रत्यय के बिना)। मैं ऐसा करने में सफल रहा हूं, लेकिन विशुद्ध रूप से जिज्ञासा से बाहर, मैं सोच रहा हूं कि क्या कोई छोटी, एक कमांड है जिसका मैं उपयोग कर सकता हूं। मूल रूप से मेरे पास एक-लाइनर awkथा, लेकिन यह लंबे समय तक था। वर्तमान में मेरे पास यह दो-लाइन स्क्रिप्ट है (एक पूर्ण फ़ाइल नाम है $1)

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

तो उदाहरण के लिए, "/path/to/somefile.txt" में "ile" के साथ समाप्त होता है $lastpart

क्या मैं किसी भी तरह से basenameऔर बिट को प्रत्यय को एक ही कमांड में जोड़ सकता हूं , और क्या इसे tailपाइप का उपयोग किए बिना (या किसी और चीज का उपयोग करने के लिए) भेजने का कोई तरीका है ? प्रत्यय अज्ञात है इसलिए मैं इसे एक पैरामीटर के रूप में आधार नहीं बना सकता basename

मुख्य लक्ष्य वास्तव में जितना संभव हो उतना छोटा नहीं है, जितना संभव हो एक नज़र में उतना ही पठनीय होना चाहिए। इस सबका वास्तविक संदर्भ सुपरसुसर पर यह प्रश्न है , जहां मैं एक उचित सरल उत्तर के साथ आने की कोशिश कर रहा हूं।


2
आप एक फ़ाइल के साथ कैसे व्यवहार करेंगे file.one.two.three? आप चाहेंगे ileया two?
terdon

@terdon twoकाम करेगा; .threeमुझे लगता है कि विस्तार होगा ।
जेसन सी

जवाबों:


6

इसके लिए एक विशिष्ट कार्य है expr:

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

यदि आप जानते हैं कि आपके फ़ाइल नामों में अपेक्षित प्रारूप है (जिसमें एक और केवल एक डॉट है और डॉट से पहले कम से कम 3 वर्ण हैं), तो यह आसान हो सकता है:

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

ध्यान दें कि यदि कोई मिलान नहीं है, तो बाहर निकलने की स्थिति गैर-शून्य होगी, लेकिन यह भी कि यदि मिलान किया गया भाग एक संख्या है जो कि 0 के लिए हल होती है (जैसे a000.txtया के लिए a-00.txt)

के साथ zsh:

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

( :tके लिए पूंछ (basename), :rके लिए बाकी (विस्तार के साथ हटा दिया))।


2
अच्छा लगा। exprएक और है जिसके साथ मुझे परिचित होना चाहिए। मुझे वास्तव में zshसमाधान पसंद हैं (मैं ${}कल के बाईं ओर नेस्टेड प्रतिस्थापन के लिए इसके समर्थन के बारे में पढ़ रहा था और इच्छा shथी कि वही हो), यह सिर्फ एक बमर है कि यह हमेशा डिफ़ॉल्ट रूप से मौजूद नहीं है।
जेसन सी

2
@JasonC - जानकारी सबसे ज्यादा मायने रखती है। जितना हो सके इसे सबसे बेहतर बनाएं - सिस्टम के पूरे बिंदु को वैसे भी पूरा करें। अगर प्रतिनिधि खरीदा भोजन मैं परेशान हो सकता है, लेकिन अधिक बार (कभी नहीं से) जानकारी घर लाता है बेकन
mikeserv

1
@mikeserv "अनुरोध: बेकन के लिए एक्सचेंज प्रतिनिधि"; मैं यहाँ आया मेटा देखो।
जेसन सी

1
@mikerserv, तुम्हारा POSIX है, केवल बिल्डिंस का उपयोग करता है और किसी भी प्रक्रिया को कांटा नहीं करता है। कमांड प्रतिस्थापन का उपयोग नहीं करने का मतलब यह भी है कि आप नई नई अनुगामी के साथ समस्याओं से बचते हैं, इसलिए यह एक अच्छा उत्तर भी है।
स्टीफन चेज़लस

1
@ माइकर्स, मेरा मतलब exprयह नहीं था कि पॉसिक्स नहीं था । यह निश्चित ही। यह शायद ही कभी अंतर्निहित है।
स्टीफन चेज़लस

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

###OUTPUT###

456

वह पहले अंतिम तीन वर्णों को $varहटाता है $var, फिर उस निष्कासन के परिणामों से हटाता है - जो अंतिम तीन वर्णों को वापस करता है $var। यहाँ कुछ उदाहरण दिए गए हैं जो विशेष रूप से यह प्रदर्शित करने के उद्देश्य से हैं कि आप ऐसा कैसे कर सकते हैं:

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

टिंग शेल मापदंडों के $IFSसाथ संयोजन setशेल शेल के माध्यम से पार्सिंग और ड्रिलिंग का एक बहुत प्रभावी साधन हो सकता है:

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

यही कारण है कि आप केवल तीन पात्रों तुरंत पिछले के बाद पहले की अवधि से ठीक पहले मिल जाएगा /में $path। आप तुरंत पिछले पूर्ववर्ती केवल पहले तीन अक्षरों को पुनः प्राप्त करना चाहते हैं .में $path (उदाहरण के लिए, अगर वहाँ एक से अधिक की संभावना है .फ़ाइल नाम में) :

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

दोनों मामलों में आप कर सकते हैं:

newvar=$(IFS...)

तथा...

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

... प्रिंट करेगा क्या इस प्रकार है .

यदि आप किसी बाहरी कार्यक्रम का उपयोग करने से गुरेज नहीं करते हैं:

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

यदि \nफ़ाइल नाम में एक ewline वर्ण का मौका है (देशी शेल समाधानों के लिए लागू नहीं है - वे वैसे भी संभालते हैं) :

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

1
यह है, धन्यवाद। मैंने प्रलेखन भी पाया है । लेकिन $baseवहां से अंतिम 3 पात्रों को प्राप्त करने के लिए , सबसे अच्छा मैं कर सकता था तीन-पंक्ति name=${var##*/} ; base=${name%%.*} ; lastpart=${base#${base%???}}। प्लस साइड में यह शुद्ध बैश है, लेकिन यह अभी भी 3 लाइनें है। ("/Tmp/file.txt" के आपके उदाहरण में "फ़ाइल" के बजाय मुझे "ile" की आवश्यकता होगी।) मैंने पैरामीटर प्रतिस्थापन के बारे में बहुत कुछ सीखा है; मुझे नहीं पता था कि यह कर सकता है ... बहुत आसान है। मुझे यह बहुत पठनीय लगता है, साथ ही व्यक्तिगत रूप से भी।
जेसन सी

1
@ जेसन - यह पूरी तरह से पोर्टेबल व्यवहार है - यह विशिष्ट नहीं है। मैं इसे पढ़ने की सलाह देता हूं ।
माइकस

1
खैर, मुझे लगता है, मैं प्रत्यय को हटाने के %बजाय उपयोग कर सकता हूं %%, और मुझे वास्तव में मार्ग को पट्टी करने की आवश्यकता नहीं है, इसलिए मुझे एक अच्छा, दो लाइन मिल सकती है noextn=${var%.*} ; lastpart=${noextn#${noextn%???}}
जेसन सी

1
@JasonC - हाँ, ऐसा लगता है कि यह काम करेगा। अगर वहाँ यह टूट जाएगा $IFSमें ${noextn}और आप विस्तार बोली नहीं है। तो, यह अधिक सुरक्षित है:lastpart=${noextn#"${noextn%???}"}
mikeserv

1
@JasonC - पिछले है, अगर आप उपयोगी ऊपर पाए, तो आप को देखने के लिए चाहते हो सकता है यह । यह पैरामीटर विस्तार के अन्य रूपों से संबंधित है और उस प्रश्न के अन्य उत्तर वास्तव में बहुत अच्छे हैं। और भीतर एक ही विषय पर दो अन्य उत्तरों के लिंक हैं। अगर तुम चाहते हो।
मिकसेर

4

यदि आप उपयोग कर सकते हैं perl:

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

वह शांत है। ny वोट मिला
चाटुकार

थोड़ा और संक्षिप्त perl -e 'shift =~ /(.{3})\.[^.]*$/ && print $1' $filename:। basenameयदि फ़ाइल नाम में कोई प्रत्यय नहीं है, लेकिन पथ में कुछ निर्देशिका होती है, तो अतिरिक्त की आवश्यकता होगी।
डब्यू

@ डब्यू: यदि फ़ाइलनाम में कोई प्रत्यय नहीं है तो आपका समाधान हमेशा विफल रहता है।
congonglm

1
@Gnouc यह इरादे से था। लेकिन आप सही हैं, यह उद्देश्य के आधार पर गलत हो सकता है। वैकल्पिक:perl -e 'shift =~ m#(.{3})(?:\.[^./]*)?$# && print $1' $filename
डब्यू

2

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जरूरत नहीं है।


1

यदि पर्ल उपलब्ध है, तो मुझे लगता है कि यह अन्य समाधानों की तुलना में अधिक पठनीय हो सकता है, विशेष रूप से क्योंकि इसकी रेगेक्स भाषा अधिक अभिव्यंजक है और इसमें /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

0

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

मुझे लगता है कि यह बैश फ़ंक्शन, pathStr (), वही करेगा जो आप देख रहे हैं।

इसमें awk, sed, grep, perl या expr की आवश्यकता नहीं है। यह केवल बैश बिल्डिंस का उपयोग करता है इसलिए यह काफी तेज है।

मैंने आश्रित 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
}

संसाधन


मुझे समझ में नहीं आता है - यह पहले से ही यहाँ प्रदर्शित किया गया है कि कैसे पूरी तरह से समान रूप से किया जा सकता है - बिना bashआईएमएस के - इसके मुकाबले सरल प्रतीत होता है। इसके अलावा, क्या है ${#@}?
मिकसेर्व

यह केवल कार्यक्षमता को पुन: प्रयोज्य फ़ंक्शन में पैकेज करता है। पुनः: $ {# @} ... सरणियों और उनके तत्वों को जोड़कर पूर्ण चर संकेतन $ {} की आवश्यकता होती है। $ @ तर्कों की 'सरणी' है। $ {# @} तर्कों की संख्या के लिए बैश सिंटैक्स है।
डॉकस्वलगर

नहीं, $#तर्कों की संख्या के लिए वाक्यविन्यास है, और इसका उपयोग यहां कहीं और भी किया गया है।
मिकसेर्व

आप सही हैं कि "$ #" "तर्कों की संख्या" के लिए व्यापक रूप से प्रलेखित सिस्टैक्स है। हालाँकि, मैंने अभी यह कहा है कि "$ {# @}" समान है। मैं स्थितिगत तर्कों और सरणियों के बीच अंतर और समानता के साथ प्रयोग करने के बाद उस पर घाव करता हूं। बाद में सरणी सिंटैक्स से आता है जो जाहिरा तौर पर छोटे, सरल "$ #" सिंटैक्स का एक पर्याय है। मैंने "$ #" का उपयोग करने के लिए बदल दिया है और argsNumber () को प्रलेखित किया है। धन्यवाद!
DocSalvager

${#@}अधिकांश मामलों में समतुल्य नहीं है - POSIX कल्पना किसी भी पैरामीटर विस्तार के परिणामों को $@या तो $*अनिर्दिष्ट, दुर्भाग्य से बताती है । यह में काम कर सकते हैं bash, लेकिन यह एक विश्वसनीय सुविधा नहीं है, मुझे लगता है कि मैं क्या कहने की कोशिश कर रहा हूँ है।
mikeserv
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.